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/AnsiTerminal.h"
32 #include "lldb/Utility/Predicate.h"
33 #include "lldb/Utility/Status.h"
34 #include "lldb/Utility/StreamString.h"
35 #include "lldb/Utility/StringList.h"
36 #include "lldb/lldb-forward.h"
37 
38 #include "lldb/Interpreter/CommandCompletions.h"
39 #include "lldb/Interpreter/CommandInterpreter.h"
40 #include "lldb/Interpreter/OptionGroupPlatform.h"
41 
42 #if LLDB_ENABLE_CURSES
43 #include "lldb/Breakpoint/BreakpointLocation.h"
44 #include "lldb/Core/Module.h"
45 #include "lldb/Core/PluginManager.h"
46 #include "lldb/Core/ValueObject.h"
47 #include "lldb/Core/ValueObjectRegister.h"
48 #include "lldb/Symbol/Block.h"
49 #include "lldb/Symbol/CompileUnit.h"
50 #include "lldb/Symbol/Function.h"
51 #include "lldb/Symbol/Symbol.h"
52 #include "lldb/Symbol/VariableList.h"
53 #include "lldb/Target/Process.h"
54 #include "lldb/Target/RegisterContext.h"
55 #include "lldb/Target/StackFrame.h"
56 #include "lldb/Target/StopInfo.h"
57 #include "lldb/Target/Target.h"
58 #include "lldb/Target/Thread.h"
59 #include "lldb/Utility/State.h"
60 #endif
61 
62 #include "llvm/ADT/StringRef.h"
63 
64 #ifdef _WIN32
65 #include "lldb/Host/windows/windows.h"
66 #endif
67 
68 #include <memory>
69 #include <mutex>
70 
71 #include <cassert>
72 #include <cctype>
73 #include <cerrno>
74 #include <cstdint>
75 #include <cstdio>
76 #include <cstring>
77 #include <functional>
78 #include <optional>
79 #include <type_traits>
80 
81 using namespace lldb;
82 using namespace lldb_private;
83 using llvm::StringRef;
84 
85 // we may want curses to be disabled for some builds for instance, windows
86 #if LLDB_ENABLE_CURSES
87 
88 #define KEY_CTRL_A 1
89 #define KEY_CTRL_E 5
90 #define KEY_CTRL_K 11
91 #define KEY_RETURN 10
92 #define KEY_ESCAPE 27
93 #define KEY_DELETE 127
94 
95 #define KEY_SHIFT_TAB (KEY_MAX + 1)
96 #define KEY_ALT_ENTER (KEY_MAX + 2)
97 
98 namespace curses {
99 class Menu;
100 class MenuDelegate;
101 class Window;
102 class WindowDelegate;
103 typedef std::shared_ptr<Menu> MenuSP;
104 typedef std::shared_ptr<MenuDelegate> MenuDelegateSP;
105 typedef std::shared_ptr<Window> WindowSP;
106 typedef std::shared_ptr<WindowDelegate> WindowDelegateSP;
107 typedef std::vector<MenuSP> Menus;
108 typedef std::vector<WindowSP> Windows;
109 typedef std::vector<WindowDelegateSP> WindowDelegates;
110 
111 #if 0
112 type summary add -s "x=${var.x}, y=${var.y}" curses::Point
113 type summary add -s "w=${var.width}, h=${var.height}" curses::Size
114 type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect
115 #endif
116 
117 struct Point {
118   int x;
119   int y;
120 
Pointcurses::Point121   Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}
122 
Clearcurses::Point123   void Clear() {
124     x = 0;
125     y = 0;
126   }
127 
operator +=curses::Point128   Point &operator+=(const Point &rhs) {
129     x += rhs.x;
130     y += rhs.y;
131     return *this;
132   }
133 
Dumpcurses::Point134   void Dump() { printf("(x=%i, y=%i)\n", x, y); }
135 };
136 
operator ==(const Point & lhs,const Point & rhs)137 bool operator==(const Point &lhs, const Point &rhs) {
138   return lhs.x == rhs.x && lhs.y == rhs.y;
139 }
140 
operator !=(const Point & lhs,const Point & rhs)141 bool operator!=(const Point &lhs, const Point &rhs) {
142   return lhs.x != rhs.x || lhs.y != rhs.y;
143 }
144 
145 struct Size {
146   int width;
147   int height;
Sizecurses::Size148   Size(int w = 0, int h = 0) : width(w), height(h) {}
149 
Clearcurses::Size150   void Clear() {
151     width = 0;
152     height = 0;
153   }
154 
Dumpcurses::Size155   void Dump() { printf("(w=%i, h=%i)\n", width, height); }
156 };
157 
operator ==(const Size & lhs,const Size & rhs)158 bool operator==(const Size &lhs, const Size &rhs) {
159   return lhs.width == rhs.width && lhs.height == rhs.height;
160 }
161 
operator !=(const Size & lhs,const Size & rhs)162 bool operator!=(const Size &lhs, const Size &rhs) {
163   return lhs.width != rhs.width || lhs.height != rhs.height;
164 }
165 
166 struct Rect {
167   Point origin;
168   Size size;
169 
Rectcurses::Rect170   Rect() : origin(), size() {}
171 
Rectcurses::Rect172   Rect(const Point &p, const Size &s) : origin(p), size(s) {}
173 
Clearcurses::Rect174   void Clear() {
175     origin.Clear();
176     size.Clear();
177   }
178 
Dumpcurses::Rect179   void Dump() {
180     printf("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width,
181            size.height);
182   }
183 
Insetcurses::Rect184   void Inset(int w, int h) {
185     if (size.width > w * 2)
186       size.width -= w * 2;
187     origin.x += w;
188 
189     if (size.height > h * 2)
190       size.height -= h * 2;
191     origin.y += h;
192   }
193 
194   // Return a status bar rectangle which is the last line of this rectangle.
195   // This rectangle will be modified to not include the status bar area.
MakeStatusBarcurses::Rect196   Rect MakeStatusBar() {
197     Rect status_bar;
198     if (size.height > 1) {
199       status_bar.origin.x = origin.x;
200       status_bar.origin.y = size.height;
201       status_bar.size.width = size.width;
202       status_bar.size.height = 1;
203       --size.height;
204     }
205     return status_bar;
206   }
207 
208   // Return a menubar rectangle which is the first line of this rectangle. This
209   // rectangle will be modified to not include the menubar area.
MakeMenuBarcurses::Rect210   Rect MakeMenuBar() {
211     Rect menubar;
212     if (size.height > 1) {
213       menubar.origin.x = origin.x;
214       menubar.origin.y = origin.y;
215       menubar.size.width = size.width;
216       menubar.size.height = 1;
217       ++origin.y;
218       --size.height;
219     }
220     return menubar;
221   }
222 
HorizontalSplitPercentagecurses::Rect223   void HorizontalSplitPercentage(float top_percentage, Rect &top,
224                                  Rect &bottom) const {
225     float top_height = top_percentage * size.height;
226     HorizontalSplit(top_height, top, bottom);
227   }
228 
HorizontalSplitcurses::Rect229   void HorizontalSplit(int top_height, Rect &top, Rect &bottom) const {
230     top = *this;
231     if (top_height < size.height) {
232       top.size.height = top_height;
233       bottom.origin.x = origin.x;
234       bottom.origin.y = origin.y + top.size.height;
235       bottom.size.width = size.width;
236       bottom.size.height = size.height - top.size.height;
237     } else {
238       bottom.Clear();
239     }
240   }
241 
VerticalSplitPercentagecurses::Rect242   void VerticalSplitPercentage(float left_percentage, Rect &left,
243                                Rect &right) const {
244     float left_width = left_percentage * size.width;
245     VerticalSplit(left_width, left, right);
246   }
247 
VerticalSplitcurses::Rect248   void VerticalSplit(int left_width, Rect &left, Rect &right) const {
249     left = *this;
250     if (left_width < size.width) {
251       left.size.width = left_width;
252       right.origin.x = origin.x + left.size.width;
253       right.origin.y = origin.y;
254       right.size.width = size.width - left.size.width;
255       right.size.height = size.height;
256     } else {
257       right.Clear();
258     }
259   }
260 };
261 
operator ==(const Rect & lhs,const Rect & rhs)262 bool operator==(const Rect &lhs, const Rect &rhs) {
263   return lhs.origin == rhs.origin && lhs.size == rhs.size;
264 }
265 
operator !=(const Rect & lhs,const Rect & rhs)266 bool operator!=(const Rect &lhs, const Rect &rhs) {
267   return lhs.origin != rhs.origin || lhs.size != rhs.size;
268 }
269 
270 enum HandleCharResult {
271   eKeyNotHandled = 0,
272   eKeyHandled = 1,
273   eQuitApplication = 2
274 };
275 
276 enum class MenuActionResult {
277   Handled,
278   NotHandled,
279   Quit // Exit all menus and quit
280 };
281 
282 struct KeyHelp {
283   int ch;
284   const char *description;
285 };
286 
287 // COLOR_PAIR index names
288 enum {
289   // First 16 colors are 8 black background and 8 blue background colors,
290   // needed by OutputColoredStringTruncated().
291   BlackOnBlack = 1,
292   RedOnBlack,
293   GreenOnBlack,
294   YellowOnBlack,
295   BlueOnBlack,
296   MagentaOnBlack,
297   CyanOnBlack,
298   WhiteOnBlack,
299   BlackOnBlue,
300   RedOnBlue,
301   GreenOnBlue,
302   YellowOnBlue,
303   BlueOnBlue,
304   MagentaOnBlue,
305   CyanOnBlue,
306   WhiteOnBlue,
307   // Other colors, as needed.
308   BlackOnWhite,
309   MagentaOnWhite,
310   LastColorPairIndex = MagentaOnWhite
311 };
312 
313 class WindowDelegate {
314 public:
315   virtual ~WindowDelegate() = default;
316 
WindowDelegateDraw(Window & window,bool force)317   virtual bool WindowDelegateDraw(Window &window, bool force) {
318     return false; // Drawing not handled
319   }
320 
WindowDelegateHandleChar(Window & window,int key)321   virtual HandleCharResult WindowDelegateHandleChar(Window &window, int key) {
322     return eKeyNotHandled;
323   }
324 
WindowDelegateGetHelpText()325   virtual const char *WindowDelegateGetHelpText() { return nullptr; }
326 
WindowDelegateGetKeyHelp()327   virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; }
328 };
329 
330 class HelpDialogDelegate : public WindowDelegate {
331 public:
332   HelpDialogDelegate(const char *text, KeyHelp *key_help_array);
333 
334   ~HelpDialogDelegate() override;
335 
336   bool WindowDelegateDraw(Window &window, bool force) override;
337 
338   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
339 
GetNumLines() const340   size_t GetNumLines() const { return m_text.GetSize(); }
341 
GetMaxLineLength() const342   size_t GetMaxLineLength() const { return m_text.GetMaxStringLength(); }
343 
344 protected:
345   StringList m_text;
346   int m_first_visible_line = 0;
347 };
348 
349 // A surface is an abstraction for something than can be drawn on. The surface
350 // have a width, a height, a cursor position, and a multitude of drawing
351 // operations. This type should be sub-classed to get an actually useful ncurses
352 // object, such as a Window or a Pad.
353 class Surface {
354 public:
355   enum class Type { Window, Pad };
356 
Surface(Surface::Type type)357   Surface(Surface::Type type) : m_type(type) {}
358 
get()359   WINDOW *get() { return m_window; }
360 
operator WINDOW*()361   operator WINDOW *() { return m_window; }
362 
SubSurface(Rect bounds)363   Surface SubSurface(Rect bounds) {
364     Surface subSurface(m_type);
365     if (m_type == Type::Pad)
366       subSurface.m_window =
367           ::subpad(m_window, bounds.size.height, bounds.size.width,
368                    bounds.origin.y, bounds.origin.x);
369     else
370       subSurface.m_window =
371           ::derwin(m_window, bounds.size.height, bounds.size.width,
372                    bounds.origin.y, bounds.origin.x);
373     return subSurface;
374   }
375 
376   // Copy a region of the surface to another surface.
CopyToSurface(Surface & target,Point source_origin,Point target_origin,Size size)377   void CopyToSurface(Surface &target, Point source_origin, Point target_origin,
378                      Size size) {
379     ::copywin(m_window, target.get(), source_origin.y, source_origin.x,
380               target_origin.y, target_origin.x,
381               target_origin.y + size.height - 1,
382               target_origin.x + size.width - 1, false);
383   }
384 
GetCursorX() const385   int GetCursorX() const { return getcurx(m_window); }
GetCursorY() const386   int GetCursorY() const { return getcury(m_window); }
MoveCursor(int x,int y)387   void MoveCursor(int x, int y) { ::wmove(m_window, y, x); }
388 
AttributeOn(attr_t attr)389   void AttributeOn(attr_t attr) { ::wattron(m_window, attr); }
AttributeOff(attr_t attr)390   void AttributeOff(attr_t attr) { ::wattroff(m_window, attr); }
391 
GetMaxX() const392   int GetMaxX() const { return getmaxx(m_window); }
GetMaxY() const393   int GetMaxY() const { return getmaxy(m_window); }
GetWidth() const394   int GetWidth() const { return GetMaxX(); }
GetHeight() const395   int GetHeight() const { return GetMaxY(); }
GetSize() const396   Size GetSize() const { return Size(GetWidth(), GetHeight()); }
397   // Get a zero origin rectangle width the surface size.
GetFrame() const398   Rect GetFrame() const { return Rect(Point(), GetSize()); }
399 
Clear()400   void Clear() { ::wclear(m_window); }
Erase()401   void Erase() { ::werase(m_window); }
402 
SetBackground(int color_pair_idx)403   void SetBackground(int color_pair_idx) {
404     ::wbkgd(m_window, COLOR_PAIR(color_pair_idx));
405   }
406 
PutChar(int ch)407   void PutChar(int ch) { ::waddch(m_window, ch); }
PutCString(const char * s,int len=-1)408   void PutCString(const char *s, int len = -1) { ::waddnstr(m_window, s, len); }
409 
PutCStringTruncated(int right_pad,const char * s,int len=-1)410   void PutCStringTruncated(int right_pad, const char *s, int len = -1) {
411     int bytes_left = GetWidth() - GetCursorX();
412     if (bytes_left > right_pad) {
413       bytes_left -= right_pad;
414       ::waddnstr(m_window, s, len < 0 ? bytes_left : std::min(bytes_left, len));
415     }
416   }
417 
Printf(const char * format,...)418   void Printf(const char *format, ...) __attribute__((format(printf, 2, 3))) {
419     va_list args;
420     va_start(args, format);
421     vw_printw(m_window, format, args);
422     va_end(args);
423   }
424 
PrintfTruncated(int right_pad,const char * format,...)425   void PrintfTruncated(int right_pad, const char *format, ...)
426       __attribute__((format(printf, 3, 4))) {
427     va_list args;
428     va_start(args, format);
429     StreamString strm;
430     strm.PrintfVarArg(format, args);
431     va_end(args);
432     PutCStringTruncated(right_pad, strm.GetData());
433   }
434 
VerticalLine(int n,chtype v_char=ACS_VLINE)435   void VerticalLine(int n, chtype v_char = ACS_VLINE) {
436     ::wvline(m_window, v_char, n);
437   }
HorizontalLine(int n,chtype h_char=ACS_HLINE)438   void HorizontalLine(int n, chtype h_char = ACS_HLINE) {
439     ::whline(m_window, h_char, n);
440   }
Box(chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)441   void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
442     ::box(m_window, v_char, h_char);
443   }
444 
TitledBox(const char * title,chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)445   void TitledBox(const char *title, chtype v_char = ACS_VLINE,
446                  chtype h_char = ACS_HLINE) {
447     Box(v_char, h_char);
448     int title_offset = 2;
449     MoveCursor(title_offset, 0);
450     PutChar('[');
451     PutCString(title, GetWidth() - title_offset);
452     PutChar(']');
453   }
454 
Box(const Rect & bounds,chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)455   void Box(const Rect &bounds, chtype v_char = ACS_VLINE,
456            chtype h_char = ACS_HLINE) {
457     MoveCursor(bounds.origin.x, bounds.origin.y);
458     VerticalLine(bounds.size.height);
459     HorizontalLine(bounds.size.width);
460     PutChar(ACS_ULCORNER);
461 
462     MoveCursor(bounds.origin.x + bounds.size.width - 1, bounds.origin.y);
463     VerticalLine(bounds.size.height);
464     PutChar(ACS_URCORNER);
465 
466     MoveCursor(bounds.origin.x, bounds.origin.y + bounds.size.height - 1);
467     HorizontalLine(bounds.size.width);
468     PutChar(ACS_LLCORNER);
469 
470     MoveCursor(bounds.origin.x + bounds.size.width - 1,
471                bounds.origin.y + bounds.size.height - 1);
472     PutChar(ACS_LRCORNER);
473   }
474 
TitledBox(const Rect & bounds,const char * title,chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)475   void TitledBox(const Rect &bounds, const char *title,
476                  chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
477     Box(bounds, v_char, h_char);
478     int title_offset = 2;
479     MoveCursor(bounds.origin.x + title_offset, bounds.origin.y);
480     PutChar('[');
481     PutCString(title, bounds.size.width - title_offset);
482     PutChar(']');
483   }
484 
485   // Curses doesn't allow direct output of color escape sequences, but that's
486   // how we get source lines from the Highligher class. Read the line and
487   // convert color escape sequences to curses color attributes. Use
488   // first_skip_count to skip leading visible characters. Returns false if all
489   // visible characters were skipped due to first_skip_count.
OutputColoredStringTruncated(int right_pad,StringRef string,size_t skip_first_count,bool use_blue_background)490   bool OutputColoredStringTruncated(int right_pad, StringRef string,
491                                     size_t skip_first_count,
492                                     bool use_blue_background) {
493     attr_t saved_attr;
494     short saved_pair;
495     bool result = false;
496     wattr_get(m_window, &saved_attr, &saved_pair, nullptr);
497     if (use_blue_background)
498       ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
499     while (!string.empty()) {
500       size_t esc_pos = string.find(ANSI_ESC_START);
501       if (esc_pos == StringRef::npos) {
502         string = string.substr(skip_first_count);
503         if (!string.empty()) {
504           PutCStringTruncated(right_pad, string.data(), string.size());
505           result = true;
506         }
507         break;
508       }
509       if (esc_pos > 0) {
510         if (skip_first_count > 0) {
511           int skip = std::min(esc_pos, skip_first_count);
512           string = string.substr(skip);
513           skip_first_count -= skip;
514           esc_pos -= skip;
515         }
516         if (esc_pos > 0) {
517           PutCStringTruncated(right_pad, string.data(), esc_pos);
518           result = true;
519           string = string.drop_front(esc_pos);
520         }
521       }
522       bool consumed = string.consume_front(ANSI_ESC_START);
523       assert(consumed);
524       UNUSED_IF_ASSERT_DISABLED(consumed);
525       // This is written to match our Highlighter classes, which seem to
526       // generate only foreground color escape sequences. If necessary, this
527       // will need to be extended.
528       // Only 8 basic foreground colors, underline and reset, our Highlighter
529       // doesn't use anything else.
530       int value;
531       if (!!string.consumeInteger(10, value) || // Returns false on success.
532           !(value == 0 || value == ANSI_CTRL_UNDERLINE ||
533             (value >= ANSI_FG_COLOR_BLACK && value <= ANSI_FG_COLOR_WHITE))) {
534         llvm::errs() << "No valid color code in color escape sequence.\n";
535         continue;
536       }
537       if (!string.consume_front(ANSI_ESC_END)) {
538         llvm::errs() << "Missing '" << ANSI_ESC_END
539                      << "' in color escape sequence.\n";
540         continue;
541       }
542       if (value == 0) { // Reset.
543         wattr_set(m_window, saved_attr, saved_pair, nullptr);
544         if (use_blue_background)
545           ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
546       } else if (value == ANSI_CTRL_UNDERLINE) {
547         ::wattron(m_window, A_UNDERLINE);
548       } else {
549         // Mapped directly to first 16 color pairs (black/blue background).
550         ::wattron(m_window, COLOR_PAIR(value - ANSI_FG_COLOR_BLACK + 1 +
551                                        (use_blue_background ? 8 : 0)));
552       }
553     }
554     wattr_set(m_window, saved_attr, saved_pair, nullptr);
555     return result;
556   }
557 
558 protected:
559   Type m_type;
560   WINDOW *m_window = nullptr;
561 };
562 
563 class Pad : public Surface {
564 public:
Pad(Size size)565   Pad(Size size) : Surface(Surface::Type::Pad) {
566     m_window = ::newpad(size.height, size.width);
567   }
568 
~Pad()569   ~Pad() { ::delwin(m_window); }
570 };
571 
572 class Window : public Surface {
573 public:
Window(const char * name)574   Window(const char *name)
575       : Surface(Surface::Type::Window), m_name(name), m_panel(nullptr),
576         m_parent(nullptr), m_subwindows(), m_delegate_sp(),
577         m_curr_active_window_idx(UINT32_MAX),
578         m_prev_active_window_idx(UINT32_MAX), m_delete(false),
579         m_needs_update(true), m_can_activate(true), m_is_subwin(false) {}
580 
Window(const char * name,WINDOW * w,bool del=true)581   Window(const char *name, WINDOW *w, bool del = true)
582       : Surface(Surface::Type::Window), m_name(name), m_panel(nullptr),
583         m_parent(nullptr), m_subwindows(), m_delegate_sp(),
584         m_curr_active_window_idx(UINT32_MAX),
585         m_prev_active_window_idx(UINT32_MAX), m_delete(del),
586         m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
587     if (w)
588       Reset(w);
589   }
590 
Window(const char * name,const Rect & bounds)591   Window(const char *name, const Rect &bounds)
592       : Surface(Surface::Type::Window), m_name(name), m_panel(nullptr),
593         m_parent(nullptr), m_subwindows(), m_delegate_sp(),
594         m_curr_active_window_idx(UINT32_MAX),
595         m_prev_active_window_idx(UINT32_MAX), m_delete(false),
596         m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
597     Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y,
598                    bounds.origin.y));
599   }
600 
~Window()601   virtual ~Window() {
602     RemoveSubWindows();
603     Reset();
604   }
605 
Reset(WINDOW * w=nullptr,bool del=true)606   void Reset(WINDOW *w = nullptr, bool del = true) {
607     if (m_window == w)
608       return;
609 
610     if (m_panel) {
611       ::del_panel(m_panel);
612       m_panel = nullptr;
613     }
614     if (m_window && m_delete) {
615       ::delwin(m_window);
616       m_window = nullptr;
617       m_delete = false;
618     }
619     if (w) {
620       m_window = w;
621       m_panel = ::new_panel(m_window);
622       m_delete = del;
623     }
624   }
625 
626   // Get the rectangle in our parent window
GetBounds() const627   Rect GetBounds() const { return Rect(GetParentOrigin(), GetSize()); }
628 
GetCenteredRect(int width,int height)629   Rect GetCenteredRect(int width, int height) {
630     Size size = GetSize();
631     width = std::min(size.width, width);
632     height = std::min(size.height, height);
633     int x = (size.width - width) / 2;
634     int y = (size.height - height) / 2;
635     return Rect(Point(x, y), Size(width, height));
636   }
637 
GetChar()638   int GetChar() { return ::wgetch(m_window); }
GetParentOrigin() const639   Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); }
GetParentX() const640   int GetParentX() const { return getparx(m_window); }
GetParentY() const641   int GetParentY() const { return getpary(m_window); }
MoveWindow(int x,int y)642   void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); }
Resize(int w,int h)643   void Resize(int w, int h) { ::wresize(m_window, h, w); }
Resize(const Size & size)644   void Resize(const Size &size) {
645     ::wresize(m_window, size.height, size.width);
646   }
MoveWindow(const Point & origin)647   void MoveWindow(const Point &origin) {
648     const bool moving_window = origin != GetParentOrigin();
649     if (m_is_subwin && moving_window) {
650       // Can't move subwindows, must delete and re-create
651       Size size = GetSize();
652       Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y,
653                      origin.x),
654             true);
655     } else {
656       ::mvwin(m_window, origin.y, origin.x);
657     }
658   }
659 
SetBounds(const Rect & bounds)660   void SetBounds(const Rect &bounds) {
661     const bool moving_window = bounds.origin != GetParentOrigin();
662     if (m_is_subwin && moving_window) {
663       // Can't move subwindows, must delete and re-create
664       Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width,
665                      bounds.origin.y, bounds.origin.x),
666             true);
667     } else {
668       if (moving_window)
669         MoveWindow(bounds.origin);
670       Resize(bounds.size);
671     }
672   }
673 
Touch()674   void Touch() {
675     ::touchwin(m_window);
676     if (m_parent)
677       m_parent->Touch();
678   }
679 
CreateSubWindow(const char * name,const Rect & bounds,bool make_active)680   WindowSP CreateSubWindow(const char *name, const Rect &bounds,
681                            bool make_active) {
682     auto get_window = [this, &bounds]() {
683       return m_window
684                  ? ::subwin(m_window, bounds.size.height, bounds.size.width,
685                             bounds.origin.y, bounds.origin.x)
686                  : ::newwin(bounds.size.height, bounds.size.width,
687                             bounds.origin.y, bounds.origin.x);
688     };
689     WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true);
690     subwindow_sp->m_is_subwin = subwindow_sp.operator bool();
691     subwindow_sp->m_parent = this;
692     if (make_active) {
693       m_prev_active_window_idx = m_curr_active_window_idx;
694       m_curr_active_window_idx = m_subwindows.size();
695     }
696     m_subwindows.push_back(subwindow_sp);
697     ::top_panel(subwindow_sp->m_panel);
698     m_needs_update = true;
699     return subwindow_sp;
700   }
701 
RemoveSubWindow(Window * window)702   bool RemoveSubWindow(Window *window) {
703     Windows::iterator pos, end = m_subwindows.end();
704     size_t i = 0;
705     for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
706       if ((*pos).get() == window) {
707         if (m_prev_active_window_idx == i)
708           m_prev_active_window_idx = UINT32_MAX;
709         else if (m_prev_active_window_idx != UINT32_MAX &&
710                  m_prev_active_window_idx > i)
711           --m_prev_active_window_idx;
712 
713         if (m_curr_active_window_idx == i)
714           m_curr_active_window_idx = UINT32_MAX;
715         else if (m_curr_active_window_idx != UINT32_MAX &&
716                  m_curr_active_window_idx > i)
717           --m_curr_active_window_idx;
718         window->Erase();
719         m_subwindows.erase(pos);
720         m_needs_update = true;
721         if (m_parent)
722           m_parent->Touch();
723         else
724           ::touchwin(stdscr);
725         return true;
726       }
727     }
728     return false;
729   }
730 
FindSubWindow(const char * name)731   WindowSP FindSubWindow(const char *name) {
732     Windows::iterator pos, end = m_subwindows.end();
733     size_t i = 0;
734     for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
735       if ((*pos)->m_name == name)
736         return *pos;
737     }
738     return WindowSP();
739   }
740 
RemoveSubWindows()741   void RemoveSubWindows() {
742     m_curr_active_window_idx = UINT32_MAX;
743     m_prev_active_window_idx = UINT32_MAX;
744     for (Windows::iterator pos = m_subwindows.begin();
745          pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) {
746       (*pos)->Erase();
747     }
748     if (m_parent)
749       m_parent->Touch();
750     else
751       ::touchwin(stdscr);
752   }
753 
754   // Window drawing utilities
DrawTitleBox(const char * title,const char * bottom_message=nullptr)755   void DrawTitleBox(const char *title, const char *bottom_message = nullptr) {
756     attr_t attr = 0;
757     if (IsActive())
758       attr = A_BOLD | COLOR_PAIR(BlackOnWhite);
759     else
760       attr = 0;
761     if (attr)
762       AttributeOn(attr);
763 
764     Box();
765     MoveCursor(3, 0);
766 
767     if (title && title[0]) {
768       PutChar('<');
769       PutCString(title);
770       PutChar('>');
771     }
772 
773     if (bottom_message && bottom_message[0]) {
774       int bottom_message_length = strlen(bottom_message);
775       int x = GetWidth() - 3 - (bottom_message_length + 2);
776 
777       if (x > 0) {
778         MoveCursor(x, GetHeight() - 1);
779         PutChar('[');
780         PutCString(bottom_message);
781         PutChar(']');
782       } else {
783         MoveCursor(1, GetHeight() - 1);
784         PutChar('[');
785         PutCStringTruncated(1, bottom_message);
786       }
787     }
788     if (attr)
789       AttributeOff(attr);
790   }
791 
Draw(bool force)792   virtual void Draw(bool force) {
793     if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force))
794       return;
795 
796     for (auto &subwindow_sp : m_subwindows)
797       subwindow_sp->Draw(force);
798   }
799 
CreateHelpSubwindow()800   bool CreateHelpSubwindow() {
801     if (m_delegate_sp) {
802       const char *text = m_delegate_sp->WindowDelegateGetHelpText();
803       KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp();
804       if ((text && text[0]) || key_help) {
805         std::unique_ptr<HelpDialogDelegate> help_delegate_up(
806             new HelpDialogDelegate(text, key_help));
807         const size_t num_lines = help_delegate_up->GetNumLines();
808         const size_t max_length = help_delegate_up->GetMaxLineLength();
809         Rect bounds = GetBounds();
810         bounds.Inset(1, 1);
811         if (max_length + 4 < static_cast<size_t>(bounds.size.width)) {
812           bounds.origin.x += (bounds.size.width - max_length + 4) / 2;
813           bounds.size.width = max_length + 4;
814         } else {
815           if (bounds.size.width > 100) {
816             const int inset_w = bounds.size.width / 4;
817             bounds.origin.x += inset_w;
818             bounds.size.width -= 2 * inset_w;
819           }
820         }
821 
822         if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) {
823           bounds.origin.y += (bounds.size.height - num_lines + 2) / 2;
824           bounds.size.height = num_lines + 2;
825         } else {
826           if (bounds.size.height > 100) {
827             const int inset_h = bounds.size.height / 4;
828             bounds.origin.y += inset_h;
829             bounds.size.height -= 2 * inset_h;
830           }
831         }
832         WindowSP help_window_sp;
833         Window *parent_window = GetParent();
834         if (parent_window)
835           help_window_sp = parent_window->CreateSubWindow("Help", bounds, true);
836         else
837           help_window_sp = CreateSubWindow("Help", bounds, true);
838         help_window_sp->SetDelegate(
839             WindowDelegateSP(help_delegate_up.release()));
840         return true;
841       }
842     }
843     return false;
844   }
845 
HandleChar(int key)846   virtual HandleCharResult HandleChar(int key) {
847     // Always check the active window first
848     HandleCharResult result = eKeyNotHandled;
849     WindowSP active_window_sp = GetActiveWindow();
850     if (active_window_sp) {
851       result = active_window_sp->HandleChar(key);
852       if (result != eKeyNotHandled)
853         return result;
854     }
855 
856     if (m_delegate_sp) {
857       result = m_delegate_sp->WindowDelegateHandleChar(*this, key);
858       if (result != eKeyNotHandled)
859         return result;
860     }
861 
862     // Then check for any windows that want any keys that weren't handled. This
863     // is typically only for a menubar. Make a copy of the subwindows in case
864     // any HandleChar() functions muck with the subwindows. If we don't do
865     // this, we can crash when iterating over the subwindows.
866     Windows subwindows(m_subwindows);
867     for (auto subwindow_sp : subwindows) {
868       if (!subwindow_sp->m_can_activate) {
869         HandleCharResult result = subwindow_sp->HandleChar(key);
870         if (result != eKeyNotHandled)
871           return result;
872       }
873     }
874 
875     return eKeyNotHandled;
876   }
877 
GetActiveWindow()878   WindowSP GetActiveWindow() {
879     if (!m_subwindows.empty()) {
880       if (m_curr_active_window_idx >= m_subwindows.size()) {
881         if (m_prev_active_window_idx < m_subwindows.size()) {
882           m_curr_active_window_idx = m_prev_active_window_idx;
883           m_prev_active_window_idx = UINT32_MAX;
884         } else if (IsActive()) {
885           m_prev_active_window_idx = UINT32_MAX;
886           m_curr_active_window_idx = UINT32_MAX;
887 
888           // Find first window that wants to be active if this window is active
889           const size_t num_subwindows = m_subwindows.size();
890           for (size_t i = 0; i < num_subwindows; ++i) {
891             if (m_subwindows[i]->GetCanBeActive()) {
892               m_curr_active_window_idx = i;
893               break;
894             }
895           }
896         }
897       }
898 
899       if (m_curr_active_window_idx < m_subwindows.size())
900         return m_subwindows[m_curr_active_window_idx];
901     }
902     return WindowSP();
903   }
904 
GetCanBeActive() const905   bool GetCanBeActive() const { return m_can_activate; }
906 
SetCanBeActive(bool b)907   void SetCanBeActive(bool b) { m_can_activate = b; }
908 
SetDelegate(const WindowDelegateSP & delegate_sp)909   void SetDelegate(const WindowDelegateSP &delegate_sp) {
910     m_delegate_sp = delegate_sp;
911   }
912 
GetParent() const913   Window *GetParent() const { return m_parent; }
914 
IsActive() const915   bool IsActive() const {
916     if (m_parent)
917       return m_parent->GetActiveWindow().get() == this;
918     else
919       return true; // Top level window is always active
920   }
921 
SelectNextWindowAsActive()922   void SelectNextWindowAsActive() {
923     // Move active focus to next window
924     const int num_subwindows = m_subwindows.size();
925     int start_idx = 0;
926     if (m_curr_active_window_idx != UINT32_MAX) {
927       m_prev_active_window_idx = m_curr_active_window_idx;
928       start_idx = m_curr_active_window_idx + 1;
929     }
930     for (int idx = start_idx; idx < num_subwindows; ++idx) {
931       if (m_subwindows[idx]->GetCanBeActive()) {
932         m_curr_active_window_idx = idx;
933         return;
934       }
935     }
936     for (int idx = 0; idx < start_idx; ++idx) {
937       if (m_subwindows[idx]->GetCanBeActive()) {
938         m_curr_active_window_idx = idx;
939         break;
940       }
941     }
942   }
943 
SelectPreviousWindowAsActive()944   void SelectPreviousWindowAsActive() {
945     // Move active focus to previous window
946     const int num_subwindows = m_subwindows.size();
947     int start_idx = num_subwindows - 1;
948     if (m_curr_active_window_idx != UINT32_MAX) {
949       m_prev_active_window_idx = m_curr_active_window_idx;
950       start_idx = m_curr_active_window_idx - 1;
951     }
952     for (int idx = start_idx; idx >= 0; --idx) {
953       if (m_subwindows[idx]->GetCanBeActive()) {
954         m_curr_active_window_idx = idx;
955         return;
956       }
957     }
958     for (int idx = num_subwindows - 1; idx > start_idx; --idx) {
959       if (m_subwindows[idx]->GetCanBeActive()) {
960         m_curr_active_window_idx = idx;
961         break;
962       }
963     }
964   }
965 
GetName() const966   const char *GetName() const { return m_name.c_str(); }
967 
968 protected:
969   std::string m_name;
970   PANEL *m_panel;
971   Window *m_parent;
972   Windows m_subwindows;
973   WindowDelegateSP m_delegate_sp;
974   uint32_t m_curr_active_window_idx;
975   uint32_t m_prev_active_window_idx;
976   bool m_delete;
977   bool m_needs_update;
978   bool m_can_activate;
979   bool m_is_subwin;
980 
981 private:
982   Window(const Window &) = delete;
983   const Window &operator=(const Window &) = delete;
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(Surface &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() {}
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() {}
1060 
1061   // Select the last element in the field if multiple elements exists.
FieldDelegateSelectLastElement()1062   virtual void FieldDelegateSelectLastElement() {}
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) {
1083     if (content)
1084       m_content = content;
1085   }
1086 
1087   // Text fields are drawn as titled boxes of a single line, with a possible
1088   // error messages at the end.
1089   //
1090   // __[Label]___________
1091   // |                  |
1092   // |__________________|
1093   // - Error message if it exists.
1094 
1095   // The text field has a height of 3 lines. 2 lines for borders and 1 line for
1096   // the content.
GetFieldHeight()1097   int GetFieldHeight() { return 3; }
1098 
1099   // The text field has a full height of 3 or 4 lines. 3 lines for the actual
1100   // field and an optional line for an error if it exists.
FieldDelegateGetHeight()1101   int FieldDelegateGetHeight() override {
1102     int height = GetFieldHeight();
1103     if (FieldDelegateHasError())
1104       height++;
1105     return height;
1106   }
1107 
1108   // Get the cursor X position in the surface coordinate.
GetCursorXPosition()1109   int GetCursorXPosition() { return m_cursor_position - m_first_visibile_char; }
1110 
GetContentLength()1111   int GetContentLength() { return m_content.length(); }
1112 
DrawContent(Surface & surface,bool is_selected)1113   void DrawContent(Surface &surface, bool is_selected) {
1114     UpdateScrolling(surface.GetWidth());
1115 
1116     surface.MoveCursor(0, 0);
1117     const char *text = m_content.c_str() + m_first_visibile_char;
1118     surface.PutCString(text, 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(Surface & surface,bool is_selected)1133   void DrawField(Surface &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     Surface content_surface = surface.SubSurface(content_bounds);
1139 
1140     DrawContent(content_surface, is_selected);
1141   }
1142 
DrawError(Surface & surface)1143   void DrawError(Surface &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(Surface & surface,bool is_selected)1154   void FieldDelegateDraw(Surface &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     Surface field_surface = surface.SubSurface(field_bounds);
1159     Surface error_surface = surface.SubSurface(error_bounds);
1160 
1161     DrawField(field_surface, is_selected);
1162     DrawError(error_surface);
1163   }
1164 
1165   // Get the position of the last visible character.
GetLastVisibleCharPosition(int width)1166   int GetLastVisibleCharPosition(int width) {
1167     int position = m_first_visibile_char + width - 1;
1168     return std::min(position, GetContentLength());
1169   }
1170 
UpdateScrolling(int width)1171   void UpdateScrolling(int width) {
1172     if (m_cursor_position < m_first_visibile_char) {
1173       m_first_visibile_char = m_cursor_position;
1174       return;
1175     }
1176 
1177     if (m_cursor_position > GetLastVisibleCharPosition(width))
1178       m_first_visibile_char = m_cursor_position - (width - 1);
1179   }
1180 
1181   // The cursor is allowed to move one character past the string.
1182   // m_cursor_position is in range [0, GetContentLength()].
MoveCursorRight()1183   void MoveCursorRight() {
1184     if (m_cursor_position < GetContentLength())
1185       m_cursor_position++;
1186   }
1187 
MoveCursorLeft()1188   void MoveCursorLeft() {
1189     if (m_cursor_position > 0)
1190       m_cursor_position--;
1191   }
1192 
MoveCursorToStart()1193   void MoveCursorToStart() { m_cursor_position = 0; }
1194 
MoveCursorToEnd()1195   void MoveCursorToEnd() { m_cursor_position = GetContentLength(); }
1196 
ScrollLeft()1197   void ScrollLeft() {
1198     if (m_first_visibile_char > 0)
1199       m_first_visibile_char--;
1200   }
1201 
1202   // Insert a character at the current cursor position and advance the cursor
1203   // position.
InsertChar(char character)1204   void InsertChar(char character) {
1205     m_content.insert(m_cursor_position, 1, character);
1206     m_cursor_position++;
1207     ClearError();
1208   }
1209 
1210   // Remove the character before the cursor position, retreat the cursor
1211   // position, and scroll left.
RemovePreviousChar()1212   void RemovePreviousChar() {
1213     if (m_cursor_position == 0)
1214       return;
1215 
1216     m_content.erase(m_cursor_position - 1, 1);
1217     m_cursor_position--;
1218     ScrollLeft();
1219     ClearError();
1220   }
1221 
1222   // Remove the character after the cursor position.
RemoveNextChar()1223   void RemoveNextChar() {
1224     if (m_cursor_position == GetContentLength())
1225       return;
1226 
1227     m_content.erase(m_cursor_position, 1);
1228     ClearError();
1229   }
1230 
1231   // Clear characters from the current cursor position to the end.
ClearToEnd()1232   void ClearToEnd() {
1233     m_content.erase(m_cursor_position);
1234     ClearError();
1235   }
1236 
Clear()1237   void Clear() {
1238     m_content.clear();
1239     m_cursor_position = 0;
1240     ClearError();
1241   }
1242 
1243   // True if the key represents a char that can be inserted in the field
1244   // content, false otherwise.
IsAcceptableChar(int key)1245   virtual bool IsAcceptableChar(int key) {
1246     // The behavior of isprint is undefined when the value is not representable
1247     // as an unsigned char. So explicitly check for non-ascii key codes.
1248     if (key > 127)
1249       return false;
1250     return isprint(key);
1251   }
1252 
FieldDelegateHandleChar(int key)1253   HandleCharResult FieldDelegateHandleChar(int key) override {
1254     if (IsAcceptableChar(key)) {
1255       ClearError();
1256       InsertChar((char)key);
1257       return eKeyHandled;
1258     }
1259 
1260     switch (key) {
1261     case KEY_HOME:
1262     case KEY_CTRL_A:
1263       MoveCursorToStart();
1264       return eKeyHandled;
1265     case KEY_END:
1266     case KEY_CTRL_E:
1267       MoveCursorToEnd();
1268       return eKeyHandled;
1269     case KEY_RIGHT:
1270     case KEY_SF:
1271       MoveCursorRight();
1272       return eKeyHandled;
1273     case KEY_LEFT:
1274     case KEY_SR:
1275       MoveCursorLeft();
1276       return eKeyHandled;
1277     case KEY_BACKSPACE:
1278     case KEY_DELETE:
1279       RemovePreviousChar();
1280       return eKeyHandled;
1281     case KEY_DC:
1282       RemoveNextChar();
1283       return eKeyHandled;
1284     case KEY_EOL:
1285     case KEY_CTRL_K:
1286       ClearToEnd();
1287       return eKeyHandled;
1288     case KEY_DL:
1289     case KEY_CLEAR:
1290       Clear();
1291       return eKeyHandled;
1292     default:
1293       break;
1294     }
1295     return eKeyNotHandled;
1296   }
1297 
FieldDelegateHasError()1298   bool FieldDelegateHasError() override { return !m_error.empty(); }
1299 
FieldDelegateExitCallback()1300   void FieldDelegateExitCallback() override {
1301     if (!IsSpecified() && m_required)
1302       SetError("This field is required!");
1303   }
1304 
IsSpecified()1305   bool IsSpecified() { return !m_content.empty(); }
1306 
ClearError()1307   void ClearError() { m_error.clear(); }
1308 
GetError()1309   const std::string &GetError() { return m_error; }
1310 
SetError(const char * error)1311   void SetError(const char *error) { m_error = error; }
1312 
GetText()1313   const std::string &GetText() { return m_content; }
1314 
SetText(const char * text)1315   void SetText(const char *text) {
1316     if (text == nullptr) {
1317       m_content.clear();
1318       return;
1319     }
1320     m_content = text;
1321   }
1322 
1323 protected:
1324   std::string m_label;
1325   bool m_required;
1326   // The position of the top left corner character of the border.
1327   std::string m_content;
1328   // The cursor position in the content string itself. Can be in the range
1329   // [0, GetContentLength()].
1330   int m_cursor_position = 0;
1331   // The index of the first visible character in the content.
1332   int m_first_visibile_char = 0;
1333   // Optional error message. If empty, field is considered to have no error.
1334   std::string m_error;
1335 };
1336 
1337 class IntegerFieldDelegate : public TextFieldDelegate {
1338 public:
IntegerFieldDelegate(const char * label,int content,bool required)1339   IntegerFieldDelegate(const char *label, int content, bool required)
1340       : TextFieldDelegate(label, std::to_string(content).c_str(), required) {}
1341 
1342   // Only accept digits.
IsAcceptableChar(int key)1343   bool IsAcceptableChar(int key) override { return isdigit(key); }
1344 
1345   // Returns the integer content of the field.
GetInteger()1346   int GetInteger() { return std::stoi(m_content); }
1347 };
1348 
1349 class FileFieldDelegate : public TextFieldDelegate {
1350 public:
FileFieldDelegate(const char * label,const char * content,bool need_to_exist,bool required)1351   FileFieldDelegate(const char *label, const char *content, bool need_to_exist,
1352                     bool required)
1353       : TextFieldDelegate(label, content, required),
1354         m_need_to_exist(need_to_exist) {}
1355 
FieldDelegateExitCallback()1356   void FieldDelegateExitCallback() override {
1357     TextFieldDelegate::FieldDelegateExitCallback();
1358     if (!IsSpecified())
1359       return;
1360 
1361     if (!m_need_to_exist)
1362       return;
1363 
1364     FileSpec file = GetResolvedFileSpec();
1365     if (!FileSystem::Instance().Exists(file)) {
1366       SetError("File doesn't exist!");
1367       return;
1368     }
1369     if (FileSystem::Instance().IsDirectory(file)) {
1370       SetError("Not a file!");
1371       return;
1372     }
1373   }
1374 
GetFileSpec()1375   FileSpec GetFileSpec() {
1376     FileSpec file_spec(GetPath());
1377     return file_spec;
1378   }
1379 
GetResolvedFileSpec()1380   FileSpec GetResolvedFileSpec() {
1381     FileSpec file_spec(GetPath());
1382     FileSystem::Instance().Resolve(file_spec);
1383     return file_spec;
1384   }
1385 
GetPath()1386   const std::string &GetPath() { return m_content; }
1387 
1388 protected:
1389   bool m_need_to_exist;
1390 };
1391 
1392 class DirectoryFieldDelegate : public TextFieldDelegate {
1393 public:
DirectoryFieldDelegate(const char * label,const char * content,bool need_to_exist,bool required)1394   DirectoryFieldDelegate(const char *label, const char *content,
1395                          bool need_to_exist, bool required)
1396       : TextFieldDelegate(label, content, required),
1397         m_need_to_exist(need_to_exist) {}
1398 
FieldDelegateExitCallback()1399   void FieldDelegateExitCallback() override {
1400     TextFieldDelegate::FieldDelegateExitCallback();
1401     if (!IsSpecified())
1402       return;
1403 
1404     if (!m_need_to_exist)
1405       return;
1406 
1407     FileSpec file = GetResolvedFileSpec();
1408     if (!FileSystem::Instance().Exists(file)) {
1409       SetError("Directory doesn't exist!");
1410       return;
1411     }
1412     if (!FileSystem::Instance().IsDirectory(file)) {
1413       SetError("Not a directory!");
1414       return;
1415     }
1416   }
1417 
GetFileSpec()1418   FileSpec GetFileSpec() {
1419     FileSpec file_spec(GetPath());
1420     return file_spec;
1421   }
1422 
GetResolvedFileSpec()1423   FileSpec GetResolvedFileSpec() {
1424     FileSpec file_spec(GetPath());
1425     FileSystem::Instance().Resolve(file_spec);
1426     return file_spec;
1427   }
1428 
GetPath()1429   const std::string &GetPath() { return m_content; }
1430 
1431 protected:
1432   bool m_need_to_exist;
1433 };
1434 
1435 class ArchFieldDelegate : public TextFieldDelegate {
1436 public:
ArchFieldDelegate(const char * label,const char * content,bool required)1437   ArchFieldDelegate(const char *label, const char *content, bool required)
1438       : TextFieldDelegate(label, content, required) {}
1439 
FieldDelegateExitCallback()1440   void FieldDelegateExitCallback() override {
1441     TextFieldDelegate::FieldDelegateExitCallback();
1442     if (!IsSpecified())
1443       return;
1444 
1445     if (!GetArchSpec().IsValid())
1446       SetError("Not a valid arch!");
1447   }
1448 
GetArchString()1449   const std::string &GetArchString() { return m_content; }
1450 
GetArchSpec()1451   ArchSpec GetArchSpec() { return ArchSpec(GetArchString()); }
1452 };
1453 
1454 class BooleanFieldDelegate : public FieldDelegate {
1455 public:
BooleanFieldDelegate(const char * label,bool content)1456   BooleanFieldDelegate(const char *label, bool content)
1457       : m_label(label), m_content(content) {}
1458 
1459   // Boolean fields are drawn as checkboxes.
1460   //
1461   // [X] Label  or [ ] Label
1462 
1463   // Boolean fields are have a single line.
FieldDelegateGetHeight()1464   int FieldDelegateGetHeight() override { return 1; }
1465 
FieldDelegateDraw(Surface & surface,bool is_selected)1466   void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1467     surface.MoveCursor(0, 0);
1468     surface.PutChar('[');
1469     if (is_selected)
1470       surface.AttributeOn(A_REVERSE);
1471     surface.PutChar(m_content ? ACS_DIAMOND : ' ');
1472     if (is_selected)
1473       surface.AttributeOff(A_REVERSE);
1474     surface.PutChar(']');
1475     surface.PutChar(' ');
1476     surface.PutCString(m_label.c_str());
1477   }
1478 
ToggleContent()1479   void ToggleContent() { m_content = !m_content; }
1480 
SetContentToTrue()1481   void SetContentToTrue() { m_content = true; }
1482 
SetContentToFalse()1483   void SetContentToFalse() { m_content = false; }
1484 
FieldDelegateHandleChar(int key)1485   HandleCharResult FieldDelegateHandleChar(int key) override {
1486     switch (key) {
1487     case 't':
1488     case '1':
1489       SetContentToTrue();
1490       return eKeyHandled;
1491     case 'f':
1492     case '0':
1493       SetContentToFalse();
1494       return eKeyHandled;
1495     case ' ':
1496     case '\r':
1497     case '\n':
1498     case KEY_ENTER:
1499       ToggleContent();
1500       return eKeyHandled;
1501     default:
1502       break;
1503     }
1504     return eKeyNotHandled;
1505   }
1506 
1507   // Returns the boolean content of the field.
GetBoolean()1508   bool GetBoolean() { return m_content; }
1509 
1510 protected:
1511   std::string m_label;
1512   bool m_content;
1513 };
1514 
1515 class ChoicesFieldDelegate : public FieldDelegate {
1516 public:
ChoicesFieldDelegate(const char * label,int number_of_visible_choices,std::vector<std::string> choices)1517   ChoicesFieldDelegate(const char *label, int number_of_visible_choices,
1518                        std::vector<std::string> choices)
1519       : m_label(label), m_number_of_visible_choices(number_of_visible_choices),
1520         m_choices(choices) {}
1521 
1522   // Choices fields are drawn as titles boxses of a number of visible choices.
1523   // The rest of the choices become visible as the user scroll. The selected
1524   // choice is denoted by a diamond as the first character.
1525   //
1526   // __[Label]___________
1527   // |-Choice 1         |
1528   // | Choice 2         |
1529   // | Choice 3         |
1530   // |__________________|
1531 
1532   // Choices field have two border characters plus the number of visible
1533   // choices.
FieldDelegateGetHeight()1534   int FieldDelegateGetHeight() override {
1535     return m_number_of_visible_choices + 2;
1536   }
1537 
GetNumberOfChoices()1538   int GetNumberOfChoices() { return m_choices.size(); }
1539 
1540   // Get the index of the last visible choice.
GetLastVisibleChoice()1541   int GetLastVisibleChoice() {
1542     int index = m_first_visibile_choice + m_number_of_visible_choices;
1543     return std::min(index, GetNumberOfChoices()) - 1;
1544   }
1545 
DrawContent(Surface & surface,bool is_selected)1546   void DrawContent(Surface &surface, bool is_selected) {
1547     int choices_to_draw = GetLastVisibleChoice() - m_first_visibile_choice + 1;
1548     for (int i = 0; i < choices_to_draw; i++) {
1549       surface.MoveCursor(0, i);
1550       int current_choice = m_first_visibile_choice + i;
1551       const char *text = m_choices[current_choice].c_str();
1552       bool highlight = is_selected && current_choice == m_choice;
1553       if (highlight)
1554         surface.AttributeOn(A_REVERSE);
1555       surface.PutChar(current_choice == m_choice ? ACS_DIAMOND : ' ');
1556       surface.PutCString(text);
1557       if (highlight)
1558         surface.AttributeOff(A_REVERSE);
1559     }
1560   }
1561 
FieldDelegateDraw(Surface & surface,bool is_selected)1562   void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1563     UpdateScrolling();
1564 
1565     surface.TitledBox(m_label.c_str());
1566 
1567     Rect content_bounds = surface.GetFrame();
1568     content_bounds.Inset(1, 1);
1569     Surface content_surface = surface.SubSurface(content_bounds);
1570 
1571     DrawContent(content_surface, is_selected);
1572   }
1573 
SelectPrevious()1574   void SelectPrevious() {
1575     if (m_choice > 0)
1576       m_choice--;
1577   }
1578 
SelectNext()1579   void SelectNext() {
1580     if (m_choice < GetNumberOfChoices() - 1)
1581       m_choice++;
1582   }
1583 
UpdateScrolling()1584   void UpdateScrolling() {
1585     if (m_choice > GetLastVisibleChoice()) {
1586       m_first_visibile_choice = m_choice - (m_number_of_visible_choices - 1);
1587       return;
1588     }
1589 
1590     if (m_choice < m_first_visibile_choice)
1591       m_first_visibile_choice = m_choice;
1592   }
1593 
FieldDelegateHandleChar(int key)1594   HandleCharResult FieldDelegateHandleChar(int key) override {
1595     switch (key) {
1596     case KEY_UP:
1597       SelectPrevious();
1598       return eKeyHandled;
1599     case KEY_DOWN:
1600       SelectNext();
1601       return eKeyHandled;
1602     default:
1603       break;
1604     }
1605     return eKeyNotHandled;
1606   }
1607 
1608   // Returns the content of the choice as a string.
GetChoiceContent()1609   std::string GetChoiceContent() { return m_choices[m_choice]; }
1610 
1611   // Returns the index of the choice.
GetChoice()1612   int GetChoice() { return m_choice; }
1613 
SetChoice(llvm::StringRef choice)1614   void SetChoice(llvm::StringRef choice) {
1615     for (int i = 0; i < GetNumberOfChoices(); i++) {
1616       if (choice == m_choices[i]) {
1617         m_choice = i;
1618         return;
1619       }
1620     }
1621   }
1622 
1623 protected:
1624   std::string m_label;
1625   int m_number_of_visible_choices;
1626   std::vector<std::string> m_choices;
1627   // The index of the selected choice.
1628   int m_choice = 0;
1629   // The index of the first visible choice in the field.
1630   int m_first_visibile_choice = 0;
1631 };
1632 
1633 class PlatformPluginFieldDelegate : public ChoicesFieldDelegate {
1634 public:
PlatformPluginFieldDelegate(Debugger & debugger)1635   PlatformPluginFieldDelegate(Debugger &debugger)
1636       : ChoicesFieldDelegate("Platform Plugin", 3, GetPossiblePluginNames()) {
1637     PlatformSP platform_sp = debugger.GetPlatformList().GetSelectedPlatform();
1638     if (platform_sp)
1639       SetChoice(platform_sp->GetPluginName());
1640   }
1641 
GetPossiblePluginNames()1642   std::vector<std::string> GetPossiblePluginNames() {
1643     std::vector<std::string> names;
1644     size_t i = 0;
1645     for (llvm::StringRef name =
1646              PluginManager::GetPlatformPluginNameAtIndex(i++);
1647          !name.empty(); name = PluginManager::GetProcessPluginNameAtIndex(i++))
1648       names.push_back(name.str());
1649     return names;
1650   }
1651 
GetPluginName()1652   std::string GetPluginName() {
1653     std::string plugin_name = GetChoiceContent();
1654     return plugin_name;
1655   }
1656 };
1657 
1658 class ProcessPluginFieldDelegate : public ChoicesFieldDelegate {
1659 public:
ProcessPluginFieldDelegate()1660   ProcessPluginFieldDelegate()
1661       : ChoicesFieldDelegate("Process Plugin", 3, GetPossiblePluginNames()) {}
1662 
GetPossiblePluginNames()1663   std::vector<std::string> GetPossiblePluginNames() {
1664     std::vector<std::string> names;
1665     names.push_back("<default>");
1666 
1667     size_t i = 0;
1668     for (llvm::StringRef name = PluginManager::GetProcessPluginNameAtIndex(i++);
1669          !name.empty(); name = PluginManager::GetProcessPluginNameAtIndex(i++))
1670       names.push_back(name.str());
1671     return names;
1672   }
1673 
GetPluginName()1674   std::string GetPluginName() {
1675     std::string plugin_name = GetChoiceContent();
1676     if (plugin_name == "<default>")
1677       return "";
1678     return plugin_name;
1679   }
1680 };
1681 
1682 class LazyBooleanFieldDelegate : public ChoicesFieldDelegate {
1683 public:
LazyBooleanFieldDelegate(const char * label,const char * calculate_label)1684   LazyBooleanFieldDelegate(const char *label, const char *calculate_label)
1685       : ChoicesFieldDelegate(label, 3, GetPossibleOptions(calculate_label)) {}
1686 
1687   static constexpr const char *kNo = "No";
1688   static constexpr const char *kYes = "Yes";
1689 
GetPossibleOptions(const char * calculate_label)1690   std::vector<std::string> GetPossibleOptions(const char *calculate_label) {
1691     std::vector<std::string> options;
1692     options.push_back(calculate_label);
1693     options.push_back(kYes);
1694     options.push_back(kNo);
1695     return options;
1696   }
1697 
GetLazyBoolean()1698   LazyBool GetLazyBoolean() {
1699     std::string choice = GetChoiceContent();
1700     if (choice == kNo)
1701       return eLazyBoolNo;
1702     else if (choice == kYes)
1703       return eLazyBoolYes;
1704     else
1705       return eLazyBoolCalculate;
1706   }
1707 };
1708 
1709 template <class T> class ListFieldDelegate : public FieldDelegate {
1710 public:
ListFieldDelegate(const char * label,T default_field)1711   ListFieldDelegate(const char *label, T default_field)
1712       : m_label(label), m_default_field(default_field),
1713         m_selection_type(SelectionType::NewButton) {}
1714 
1715   // Signify which element is selected. If a field or a remove button is
1716   // selected, then m_selection_index signifies the particular field that
1717   // is selected or the field that the remove button belongs to.
1718   enum class SelectionType { Field, RemoveButton, NewButton };
1719 
1720   // A List field is drawn as a titled box of a number of other fields of the
1721   // same type. Each field has a Remove button next to it that removes the
1722   // corresponding field. Finally, the last line contains a New button to add a
1723   // new field.
1724   //
1725   // __[Label]___________
1726   // | Field 0 [Remove] |
1727   // | Field 1 [Remove] |
1728   // | Field 2 [Remove] |
1729   // |      [New]       |
1730   // |__________________|
1731 
1732   // List fields have two lines for border characters, 1 line for the New
1733   // button, and the total height of the available fields.
FieldDelegateGetHeight()1734   int FieldDelegateGetHeight() override {
1735     // 2 border characters.
1736     int height = 2;
1737     // Total height of the fields.
1738     for (int i = 0; i < GetNumberOfFields(); i++) {
1739       height += m_fields[i].FieldDelegateGetHeight();
1740     }
1741     // A line for the New button.
1742     height++;
1743     return height;
1744   }
1745 
FieldDelegateGetScrollContext()1746   ScrollContext FieldDelegateGetScrollContext() override {
1747     int height = FieldDelegateGetHeight();
1748     if (m_selection_type == SelectionType::NewButton)
1749       return ScrollContext(height - 2, height - 1);
1750 
1751     FieldDelegate &field = m_fields[m_selection_index];
1752     ScrollContext context = field.FieldDelegateGetScrollContext();
1753 
1754     // Start at 1 because of the top border.
1755     int offset = 1;
1756     for (int i = 0; i < m_selection_index; i++) {
1757       offset += m_fields[i].FieldDelegateGetHeight();
1758     }
1759     context.Offset(offset);
1760 
1761     // If the scroll context is touching the top border, include it in the
1762     // context to show the label.
1763     if (context.start == 1)
1764       context.start--;
1765 
1766     // If the scroll context is touching the new button, include it as well as
1767     // the bottom border in the context.
1768     if (context.end == height - 3)
1769       context.end += 2;
1770 
1771     return context;
1772   }
1773 
DrawRemoveButton(Surface & surface,int highlight)1774   void DrawRemoveButton(Surface &surface, int highlight) {
1775     surface.MoveCursor(1, surface.GetHeight() / 2);
1776     if (highlight)
1777       surface.AttributeOn(A_REVERSE);
1778     surface.PutCString("[Remove]");
1779     if (highlight)
1780       surface.AttributeOff(A_REVERSE);
1781   }
1782 
DrawFields(Surface & surface,bool is_selected)1783   void DrawFields(Surface &surface, bool is_selected) {
1784     int line = 0;
1785     int width = surface.GetWidth();
1786     for (int i = 0; i < GetNumberOfFields(); i++) {
1787       int height = m_fields[i].FieldDelegateGetHeight();
1788       Rect bounds = Rect(Point(0, line), Size(width, height));
1789       Rect field_bounds, remove_button_bounds;
1790       bounds.VerticalSplit(bounds.size.width - sizeof(" [Remove]"),
1791                            field_bounds, remove_button_bounds);
1792       Surface field_surface = surface.SubSurface(field_bounds);
1793       Surface remove_button_surface = surface.SubSurface(remove_button_bounds);
1794 
1795       bool is_element_selected = m_selection_index == i && is_selected;
1796       bool is_field_selected =
1797           is_element_selected && m_selection_type == SelectionType::Field;
1798       bool is_remove_button_selected =
1799           is_element_selected &&
1800           m_selection_type == SelectionType::RemoveButton;
1801       m_fields[i].FieldDelegateDraw(field_surface, is_field_selected);
1802       DrawRemoveButton(remove_button_surface, is_remove_button_selected);
1803 
1804       line += height;
1805     }
1806   }
1807 
DrawNewButton(Surface & surface,bool is_selected)1808   void DrawNewButton(Surface &surface, bool is_selected) {
1809     const char *button_text = "[New]";
1810     int x = (surface.GetWidth() - sizeof(button_text) - 1) / 2;
1811     surface.MoveCursor(x, 0);
1812     bool highlight =
1813         is_selected && m_selection_type == SelectionType::NewButton;
1814     if (highlight)
1815       surface.AttributeOn(A_REVERSE);
1816     surface.PutCString(button_text);
1817     if (highlight)
1818       surface.AttributeOff(A_REVERSE);
1819   }
1820 
FieldDelegateDraw(Surface & surface,bool is_selected)1821   void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1822     surface.TitledBox(m_label.c_str());
1823 
1824     Rect content_bounds = surface.GetFrame();
1825     content_bounds.Inset(1, 1);
1826     Rect fields_bounds, new_button_bounds;
1827     content_bounds.HorizontalSplit(content_bounds.size.height - 1,
1828                                    fields_bounds, new_button_bounds);
1829     Surface fields_surface = surface.SubSurface(fields_bounds);
1830     Surface new_button_surface = surface.SubSurface(new_button_bounds);
1831 
1832     DrawFields(fields_surface, is_selected);
1833     DrawNewButton(new_button_surface, is_selected);
1834   }
1835 
AddNewField()1836   void AddNewField() {
1837     m_fields.push_back(m_default_field);
1838     m_selection_index = GetNumberOfFields() - 1;
1839     m_selection_type = SelectionType::Field;
1840     FieldDelegate &field = m_fields[m_selection_index];
1841     field.FieldDelegateSelectFirstElement();
1842   }
1843 
RemoveField()1844   void RemoveField() {
1845     m_fields.erase(m_fields.begin() + m_selection_index);
1846     if (m_selection_index != 0)
1847       m_selection_index--;
1848 
1849     if (GetNumberOfFields() > 0) {
1850       m_selection_type = SelectionType::Field;
1851       FieldDelegate &field = m_fields[m_selection_index];
1852       field.FieldDelegateSelectFirstElement();
1853     } else
1854       m_selection_type = SelectionType::NewButton;
1855   }
1856 
SelectNext(int key)1857   HandleCharResult SelectNext(int key) {
1858     if (m_selection_type == SelectionType::NewButton)
1859       return eKeyNotHandled;
1860 
1861     if (m_selection_type == SelectionType::RemoveButton) {
1862       if (m_selection_index == GetNumberOfFields() - 1) {
1863         m_selection_type = SelectionType::NewButton;
1864         return eKeyHandled;
1865       }
1866       m_selection_index++;
1867       m_selection_type = SelectionType::Field;
1868       FieldDelegate &next_field = m_fields[m_selection_index];
1869       next_field.FieldDelegateSelectFirstElement();
1870       return eKeyHandled;
1871     }
1872 
1873     FieldDelegate &field = m_fields[m_selection_index];
1874     if (!field.FieldDelegateOnLastOrOnlyElement()) {
1875       return field.FieldDelegateHandleChar(key);
1876     }
1877 
1878     field.FieldDelegateExitCallback();
1879 
1880     m_selection_type = SelectionType::RemoveButton;
1881     return eKeyHandled;
1882   }
1883 
SelectPrevious(int key)1884   HandleCharResult SelectPrevious(int key) {
1885     if (FieldDelegateOnFirstOrOnlyElement())
1886       return eKeyNotHandled;
1887 
1888     if (m_selection_type == SelectionType::RemoveButton) {
1889       m_selection_type = SelectionType::Field;
1890       FieldDelegate &field = m_fields[m_selection_index];
1891       field.FieldDelegateSelectLastElement();
1892       return eKeyHandled;
1893     }
1894 
1895     if (m_selection_type == SelectionType::NewButton) {
1896       m_selection_type = SelectionType::RemoveButton;
1897       m_selection_index = GetNumberOfFields() - 1;
1898       return eKeyHandled;
1899     }
1900 
1901     FieldDelegate &field = m_fields[m_selection_index];
1902     if (!field.FieldDelegateOnFirstOrOnlyElement()) {
1903       return field.FieldDelegateHandleChar(key);
1904     }
1905 
1906     field.FieldDelegateExitCallback();
1907 
1908     m_selection_type = SelectionType::RemoveButton;
1909     m_selection_index--;
1910     return eKeyHandled;
1911   }
1912 
1913   // If the last element of the field is selected and it didn't handle the key.
1914   // Select the next field or new button if the selected field is the last one.
SelectNextInList(int key)1915   HandleCharResult SelectNextInList(int key) {
1916     assert(m_selection_type == SelectionType::Field);
1917 
1918     FieldDelegate &field = m_fields[m_selection_index];
1919     if (field.FieldDelegateHandleChar(key) == eKeyHandled)
1920       return eKeyHandled;
1921 
1922     if (!field.FieldDelegateOnLastOrOnlyElement())
1923       return eKeyNotHandled;
1924 
1925     field.FieldDelegateExitCallback();
1926 
1927     if (m_selection_index == GetNumberOfFields() - 1) {
1928       m_selection_type = SelectionType::NewButton;
1929       return eKeyHandled;
1930     }
1931 
1932     m_selection_index++;
1933     FieldDelegate &next_field = m_fields[m_selection_index];
1934     next_field.FieldDelegateSelectFirstElement();
1935     return eKeyHandled;
1936   }
1937 
FieldDelegateHandleChar(int key)1938   HandleCharResult FieldDelegateHandleChar(int key) override {
1939     switch (key) {
1940     case '\r':
1941     case '\n':
1942     case KEY_ENTER:
1943       switch (m_selection_type) {
1944       case SelectionType::NewButton:
1945         AddNewField();
1946         return eKeyHandled;
1947       case SelectionType::RemoveButton:
1948         RemoveField();
1949         return eKeyHandled;
1950       case SelectionType::Field:
1951         return SelectNextInList(key);
1952       }
1953       break;
1954     case '\t':
1955       return SelectNext(key);
1956     case KEY_SHIFT_TAB:
1957       return SelectPrevious(key);
1958     default:
1959       break;
1960     }
1961 
1962     // If the key wasn't handled and one of the fields is selected, pass the key
1963     // to that field.
1964     if (m_selection_type == SelectionType::Field) {
1965       return m_fields[m_selection_index].FieldDelegateHandleChar(key);
1966     }
1967 
1968     return eKeyNotHandled;
1969   }
1970 
FieldDelegateOnLastOrOnlyElement()1971   bool FieldDelegateOnLastOrOnlyElement() override {
1972     if (m_selection_type == SelectionType::NewButton) {
1973       return true;
1974     }
1975     return false;
1976   }
1977 
FieldDelegateOnFirstOrOnlyElement()1978   bool FieldDelegateOnFirstOrOnlyElement() override {
1979     if (m_selection_type == SelectionType::NewButton &&
1980         GetNumberOfFields() == 0)
1981       return true;
1982 
1983     if (m_selection_type == SelectionType::Field && m_selection_index == 0) {
1984       FieldDelegate &field = m_fields[m_selection_index];
1985       return field.FieldDelegateOnFirstOrOnlyElement();
1986     }
1987 
1988     return false;
1989   }
1990 
FieldDelegateSelectFirstElement()1991   void FieldDelegateSelectFirstElement() override {
1992     if (GetNumberOfFields() == 0) {
1993       m_selection_type = SelectionType::NewButton;
1994       return;
1995     }
1996 
1997     m_selection_type = SelectionType::Field;
1998     m_selection_index = 0;
1999   }
2000 
FieldDelegateSelectLastElement()2001   void FieldDelegateSelectLastElement() override {
2002     m_selection_type = SelectionType::NewButton;
2003   }
2004 
GetNumberOfFields()2005   int GetNumberOfFields() { return m_fields.size(); }
2006 
2007   // Returns the form delegate at the current index.
GetField(int index)2008   T &GetField(int index) { return m_fields[index]; }
2009 
2010 protected:
2011   std::string m_label;
2012   // The default field delegate instance from which new field delegates will be
2013   // created though a copy.
2014   T m_default_field;
2015   std::vector<T> m_fields;
2016   int m_selection_index = 0;
2017   // See SelectionType class enum.
2018   SelectionType m_selection_type;
2019 };
2020 
2021 class ArgumentsFieldDelegate : public ListFieldDelegate<TextFieldDelegate> {
2022 public:
ArgumentsFieldDelegate()2023   ArgumentsFieldDelegate()
2024       : ListFieldDelegate("Arguments",
2025                           TextFieldDelegate("Argument", "", false)) {}
2026 
GetArguments()2027   Args GetArguments() {
2028     Args arguments;
2029     for (int i = 0; i < GetNumberOfFields(); i++) {
2030       arguments.AppendArgument(GetField(i).GetText());
2031     }
2032     return arguments;
2033   }
2034 
AddArguments(const Args & arguments)2035   void AddArguments(const Args &arguments) {
2036     for (size_t i = 0; i < arguments.GetArgumentCount(); i++) {
2037       AddNewField();
2038       TextFieldDelegate &field = GetField(GetNumberOfFields() - 1);
2039       field.SetText(arguments.GetArgumentAtIndex(i));
2040     }
2041   }
2042 };
2043 
2044 template <class KeyFieldDelegateType, class ValueFieldDelegateType>
2045 class MappingFieldDelegate : public FieldDelegate {
2046 public:
MappingFieldDelegate(KeyFieldDelegateType key_field,ValueFieldDelegateType value_field)2047   MappingFieldDelegate(KeyFieldDelegateType key_field,
2048                        ValueFieldDelegateType value_field)
2049       : m_key_field(key_field), m_value_field(value_field),
2050         m_selection_type(SelectionType::Key) {}
2051 
2052   // Signify which element is selected. The key field or its value field.
2053   enum class SelectionType { Key, Value };
2054 
2055   // A mapping field is drawn as two text fields with a right arrow in between.
2056   // The first field stores the key of the mapping and the second stores the
2057   // value if the mapping.
2058   //
2059   // __[Key]_____________   __[Value]___________
2060   // |                  | > |                  |
2061   // |__________________|   |__________________|
2062   // - Error message if it exists.
2063 
2064   // The mapping field has a height that is equal to the maximum height between
2065   // the key and value fields.
FieldDelegateGetHeight()2066   int FieldDelegateGetHeight() override {
2067     return std::max(m_key_field.FieldDelegateGetHeight(),
2068                     m_value_field.FieldDelegateGetHeight());
2069   }
2070 
DrawArrow(Surface & surface)2071   void DrawArrow(Surface &surface) {
2072     surface.MoveCursor(0, 1);
2073     surface.PutChar(ACS_RARROW);
2074   }
2075 
FieldDelegateDraw(Surface & surface,bool is_selected)2076   void FieldDelegateDraw(Surface &surface, bool is_selected) override {
2077     Rect bounds = surface.GetFrame();
2078     Rect key_field_bounds, arrow_and_value_field_bounds;
2079     bounds.VerticalSplit(bounds.size.width / 2, key_field_bounds,
2080                          arrow_and_value_field_bounds);
2081     Rect arrow_bounds, value_field_bounds;
2082     arrow_and_value_field_bounds.VerticalSplit(1, arrow_bounds,
2083                                                value_field_bounds);
2084 
2085     Surface key_field_surface = surface.SubSurface(key_field_bounds);
2086     Surface arrow_surface = surface.SubSurface(arrow_bounds);
2087     Surface value_field_surface = surface.SubSurface(value_field_bounds);
2088 
2089     bool key_is_selected =
2090         m_selection_type == SelectionType::Key && is_selected;
2091     m_key_field.FieldDelegateDraw(key_field_surface, key_is_selected);
2092     DrawArrow(arrow_surface);
2093     bool value_is_selected =
2094         m_selection_type == SelectionType::Value && is_selected;
2095     m_value_field.FieldDelegateDraw(value_field_surface, value_is_selected);
2096   }
2097 
SelectNext(int key)2098   HandleCharResult SelectNext(int key) {
2099     if (FieldDelegateOnLastOrOnlyElement())
2100       return eKeyNotHandled;
2101 
2102     if (!m_key_field.FieldDelegateOnLastOrOnlyElement()) {
2103       return m_key_field.FieldDelegateHandleChar(key);
2104     }
2105 
2106     m_key_field.FieldDelegateExitCallback();
2107     m_selection_type = SelectionType::Value;
2108     m_value_field.FieldDelegateSelectFirstElement();
2109     return eKeyHandled;
2110   }
2111 
SelectPrevious(int key)2112   HandleCharResult SelectPrevious(int key) {
2113     if (FieldDelegateOnFirstOrOnlyElement())
2114       return eKeyNotHandled;
2115 
2116     if (!m_value_field.FieldDelegateOnFirstOrOnlyElement()) {
2117       return m_value_field.FieldDelegateHandleChar(key);
2118     }
2119 
2120     m_value_field.FieldDelegateExitCallback();
2121     m_selection_type = SelectionType::Key;
2122     m_key_field.FieldDelegateSelectLastElement();
2123     return eKeyHandled;
2124   }
2125 
2126   // If the value field is selected, pass the key to it. If the key field is
2127   // selected, its last element is selected, and it didn't handle the key, then
2128   // select its corresponding value field.
SelectNextField(int key)2129   HandleCharResult SelectNextField(int key) {
2130     if (m_selection_type == SelectionType::Value) {
2131       return m_value_field.FieldDelegateHandleChar(key);
2132     }
2133 
2134     if (m_key_field.FieldDelegateHandleChar(key) == eKeyHandled)
2135       return eKeyHandled;
2136 
2137     if (!m_key_field.FieldDelegateOnLastOrOnlyElement())
2138       return eKeyNotHandled;
2139 
2140     m_key_field.FieldDelegateExitCallback();
2141     m_selection_type = SelectionType::Value;
2142     m_value_field.FieldDelegateSelectFirstElement();
2143     return eKeyHandled;
2144   }
2145 
FieldDelegateHandleChar(int key)2146   HandleCharResult FieldDelegateHandleChar(int key) override {
2147     switch (key) {
2148     case KEY_RETURN:
2149       return SelectNextField(key);
2150     case '\t':
2151       return SelectNext(key);
2152     case KEY_SHIFT_TAB:
2153       return SelectPrevious(key);
2154     default:
2155       break;
2156     }
2157 
2158     // If the key wasn't handled, pass the key to the selected field.
2159     if (m_selection_type == SelectionType::Key)
2160       return m_key_field.FieldDelegateHandleChar(key);
2161     else
2162       return m_value_field.FieldDelegateHandleChar(key);
2163 
2164     return eKeyNotHandled;
2165   }
2166 
FieldDelegateOnFirstOrOnlyElement()2167   bool FieldDelegateOnFirstOrOnlyElement() override {
2168     return m_selection_type == SelectionType::Key;
2169   }
2170 
FieldDelegateOnLastOrOnlyElement()2171   bool FieldDelegateOnLastOrOnlyElement() override {
2172     return m_selection_type == SelectionType::Value;
2173   }
2174 
FieldDelegateSelectFirstElement()2175   void FieldDelegateSelectFirstElement() override {
2176     m_selection_type = SelectionType::Key;
2177   }
2178 
FieldDelegateSelectLastElement()2179   void FieldDelegateSelectLastElement() override {
2180     m_selection_type = SelectionType::Value;
2181   }
2182 
FieldDelegateHasError()2183   bool FieldDelegateHasError() override {
2184     return m_key_field.FieldDelegateHasError() ||
2185            m_value_field.FieldDelegateHasError();
2186   }
2187 
GetKeyField()2188   KeyFieldDelegateType &GetKeyField() { return m_key_field; }
2189 
GetValueField()2190   ValueFieldDelegateType &GetValueField() { return m_value_field; }
2191 
2192 protected:
2193   KeyFieldDelegateType m_key_field;
2194   ValueFieldDelegateType m_value_field;
2195   // See SelectionType class enum.
2196   SelectionType m_selection_type;
2197 };
2198 
2199 class EnvironmentVariableNameFieldDelegate : public TextFieldDelegate {
2200 public:
EnvironmentVariableNameFieldDelegate(const char * content)2201   EnvironmentVariableNameFieldDelegate(const char *content)
2202       : TextFieldDelegate("Name", content, true) {}
2203 
2204   // Environment variable names can't contain an equal sign.
IsAcceptableChar(int key)2205   bool IsAcceptableChar(int key) override {
2206     return TextFieldDelegate::IsAcceptableChar(key) && key != '=';
2207   }
2208 
GetName()2209   const std::string &GetName() { return m_content; }
2210 };
2211 
2212 class EnvironmentVariableFieldDelegate
2213     : public MappingFieldDelegate<EnvironmentVariableNameFieldDelegate,
2214                                   TextFieldDelegate> {
2215 public:
EnvironmentVariableFieldDelegate()2216   EnvironmentVariableFieldDelegate()
2217       : MappingFieldDelegate(
2218             EnvironmentVariableNameFieldDelegate(""),
2219             TextFieldDelegate("Value", "", /*required=*/false)) {}
2220 
GetName()2221   const std::string &GetName() { return GetKeyField().GetName(); }
2222 
GetValue()2223   const std::string &GetValue() { return GetValueField().GetText(); }
2224 
SetName(const char * name)2225   void SetName(const char *name) { return GetKeyField().SetText(name); }
2226 
SetValue(const char * value)2227   void SetValue(const char *value) { return GetValueField().SetText(value); }
2228 };
2229 
2230 class EnvironmentVariableListFieldDelegate
2231     : public ListFieldDelegate<EnvironmentVariableFieldDelegate> {
2232 public:
EnvironmentVariableListFieldDelegate(const char * label)2233   EnvironmentVariableListFieldDelegate(const char *label)
2234       : ListFieldDelegate(label, EnvironmentVariableFieldDelegate()) {}
2235 
GetEnvironment()2236   Environment GetEnvironment() {
2237     Environment environment;
2238     for (int i = 0; i < GetNumberOfFields(); i++) {
2239       environment.insert(
2240           std::make_pair(GetField(i).GetName(), GetField(i).GetValue()));
2241     }
2242     return environment;
2243   }
2244 
AddEnvironmentVariables(const Environment & environment)2245   void AddEnvironmentVariables(const Environment &environment) {
2246     for (auto &variable : environment) {
2247       AddNewField();
2248       EnvironmentVariableFieldDelegate &field =
2249           GetField(GetNumberOfFields() - 1);
2250       field.SetName(variable.getKey().str().c_str());
2251       field.SetValue(variable.getValue().c_str());
2252     }
2253   }
2254 };
2255 
2256 class FormAction {
2257 public:
FormAction(const char * label,std::function<void (Window &)> action)2258   FormAction(const char *label, std::function<void(Window &)> action)
2259       : m_action(action) {
2260     if (label)
2261       m_label = label;
2262   }
2263 
2264   // Draw a centered [Label].
Draw(Surface & surface,bool is_selected)2265   void Draw(Surface &surface, bool is_selected) {
2266     int x = (surface.GetWidth() - m_label.length()) / 2;
2267     surface.MoveCursor(x, 0);
2268     if (is_selected)
2269       surface.AttributeOn(A_REVERSE);
2270     surface.PutChar('[');
2271     surface.PutCString(m_label.c_str());
2272     surface.PutChar(']');
2273     if (is_selected)
2274       surface.AttributeOff(A_REVERSE);
2275   }
2276 
Execute(Window & window)2277   void Execute(Window &window) { m_action(window); }
2278 
GetLabel()2279   const std::string &GetLabel() { return m_label; }
2280 
2281 protected:
2282   std::string m_label;
2283   std::function<void(Window &)> m_action;
2284 };
2285 
2286 class FormDelegate {
2287 public:
2288   FormDelegate() = default;
2289 
2290   virtual ~FormDelegate() = default;
2291 
2292   virtual std::string GetName() = 0;
2293 
UpdateFieldsVisibility()2294   virtual void UpdateFieldsVisibility() {}
2295 
GetField(uint32_t field_index)2296   FieldDelegate *GetField(uint32_t field_index) {
2297     if (field_index < m_fields.size())
2298       return m_fields[field_index].get();
2299     return nullptr;
2300   }
2301 
GetAction(int action_index)2302   FormAction &GetAction(int action_index) { return m_actions[action_index]; }
2303 
GetNumberOfFields()2304   int GetNumberOfFields() { return m_fields.size(); }
2305 
GetNumberOfActions()2306   int GetNumberOfActions() { return m_actions.size(); }
2307 
HasError()2308   bool HasError() { return !m_error.empty(); }
2309 
ClearError()2310   void ClearError() { m_error.clear(); }
2311 
GetError()2312   const std::string &GetError() { return m_error; }
2313 
SetError(const char * error)2314   void SetError(const char *error) { m_error = error; }
2315 
2316   // If all fields are valid, true is returned. Otherwise, an error message is
2317   // set and false is returned. This method is usually called at the start of an
2318   // action that requires valid fields.
CheckFieldsValidity()2319   bool CheckFieldsValidity() {
2320     for (int i = 0; i < GetNumberOfFields(); i++) {
2321       GetField(i)->FieldDelegateExitCallback();
2322       if (GetField(i)->FieldDelegateHasError()) {
2323         SetError("Some fields are invalid!");
2324         return false;
2325       }
2326     }
2327     return true;
2328   }
2329 
2330   // Factory methods to create and add fields of specific types.
2331 
AddTextField(const char * label,const char * content,bool required)2332   TextFieldDelegate *AddTextField(const char *label, const char *content,
2333                                   bool required) {
2334     TextFieldDelegate *delegate =
2335         new TextFieldDelegate(label, content, required);
2336     m_fields.push_back(FieldDelegateUP(delegate));
2337     return delegate;
2338   }
2339 
AddFileField(const char * label,const char * content,bool need_to_exist,bool required)2340   FileFieldDelegate *AddFileField(const char *label, const char *content,
2341                                   bool need_to_exist, bool required) {
2342     FileFieldDelegate *delegate =
2343         new FileFieldDelegate(label, content, need_to_exist, required);
2344     m_fields.push_back(FieldDelegateUP(delegate));
2345     return delegate;
2346   }
2347 
AddDirectoryField(const char * label,const char * content,bool need_to_exist,bool required)2348   DirectoryFieldDelegate *AddDirectoryField(const char *label,
2349                                             const char *content,
2350                                             bool need_to_exist, bool required) {
2351     DirectoryFieldDelegate *delegate =
2352         new DirectoryFieldDelegate(label, content, need_to_exist, required);
2353     m_fields.push_back(FieldDelegateUP(delegate));
2354     return delegate;
2355   }
2356 
AddArchField(const char * label,const char * content,bool required)2357   ArchFieldDelegate *AddArchField(const char *label, const char *content,
2358                                   bool required) {
2359     ArchFieldDelegate *delegate =
2360         new ArchFieldDelegate(label, content, required);
2361     m_fields.push_back(FieldDelegateUP(delegate));
2362     return delegate;
2363   }
2364 
AddIntegerField(const char * label,int content,bool required)2365   IntegerFieldDelegate *AddIntegerField(const char *label, int content,
2366                                         bool required) {
2367     IntegerFieldDelegate *delegate =
2368         new IntegerFieldDelegate(label, content, required);
2369     m_fields.push_back(FieldDelegateUP(delegate));
2370     return delegate;
2371   }
2372 
AddBooleanField(const char * label,bool content)2373   BooleanFieldDelegate *AddBooleanField(const char *label, bool content) {
2374     BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content);
2375     m_fields.push_back(FieldDelegateUP(delegate));
2376     return delegate;
2377   }
2378 
AddLazyBooleanField(const char * label,const char * calculate_label)2379   LazyBooleanFieldDelegate *AddLazyBooleanField(const char *label,
2380                                                 const char *calculate_label) {
2381     LazyBooleanFieldDelegate *delegate =
2382         new LazyBooleanFieldDelegate(label, calculate_label);
2383     m_fields.push_back(FieldDelegateUP(delegate));
2384     return delegate;
2385   }
2386 
AddChoicesField(const char * label,int height,std::vector<std::string> choices)2387   ChoicesFieldDelegate *AddChoicesField(const char *label, int height,
2388                                         std::vector<std::string> choices) {
2389     ChoicesFieldDelegate *delegate =
2390         new ChoicesFieldDelegate(label, height, choices);
2391     m_fields.push_back(FieldDelegateUP(delegate));
2392     return delegate;
2393   }
2394 
AddPlatformPluginField(Debugger & debugger)2395   PlatformPluginFieldDelegate *AddPlatformPluginField(Debugger &debugger) {
2396     PlatformPluginFieldDelegate *delegate =
2397         new PlatformPluginFieldDelegate(debugger);
2398     m_fields.push_back(FieldDelegateUP(delegate));
2399     return delegate;
2400   }
2401 
AddProcessPluginField()2402   ProcessPluginFieldDelegate *AddProcessPluginField() {
2403     ProcessPluginFieldDelegate *delegate = new ProcessPluginFieldDelegate();
2404     m_fields.push_back(FieldDelegateUP(delegate));
2405     return delegate;
2406   }
2407 
2408   template <class T>
AddListField(const char * label,T default_field)2409   ListFieldDelegate<T> *AddListField(const char *label, T default_field) {
2410     ListFieldDelegate<T> *delegate =
2411         new ListFieldDelegate<T>(label, default_field);
2412     m_fields.push_back(FieldDelegateUP(delegate));
2413     return delegate;
2414   }
2415 
AddArgumentsField()2416   ArgumentsFieldDelegate *AddArgumentsField() {
2417     ArgumentsFieldDelegate *delegate = new ArgumentsFieldDelegate();
2418     m_fields.push_back(FieldDelegateUP(delegate));
2419     return delegate;
2420   }
2421 
2422   template <class K, class V>
AddMappingField(K key_field,V value_field)2423   MappingFieldDelegate<K, V> *AddMappingField(K key_field, V value_field) {
2424     MappingFieldDelegate<K, V> *delegate =
2425         new MappingFieldDelegate<K, V>(key_field, value_field);
2426     m_fields.push_back(FieldDelegateUP(delegate));
2427     return delegate;
2428   }
2429 
2430   EnvironmentVariableNameFieldDelegate *
AddEnvironmentVariableNameField(const char * content)2431   AddEnvironmentVariableNameField(const char *content) {
2432     EnvironmentVariableNameFieldDelegate *delegate =
2433         new EnvironmentVariableNameFieldDelegate(content);
2434     m_fields.push_back(FieldDelegateUP(delegate));
2435     return delegate;
2436   }
2437 
AddEnvironmentVariableField()2438   EnvironmentVariableFieldDelegate *AddEnvironmentVariableField() {
2439     EnvironmentVariableFieldDelegate *delegate =
2440         new EnvironmentVariableFieldDelegate();
2441     m_fields.push_back(FieldDelegateUP(delegate));
2442     return delegate;
2443   }
2444 
2445   EnvironmentVariableListFieldDelegate *
AddEnvironmentVariableListField(const char * label)2446   AddEnvironmentVariableListField(const char *label) {
2447     EnvironmentVariableListFieldDelegate *delegate =
2448         new EnvironmentVariableListFieldDelegate(label);
2449     m_fields.push_back(FieldDelegateUP(delegate));
2450     return delegate;
2451   }
2452 
2453   // Factory methods for adding actions.
2454 
AddAction(const char * label,std::function<void (Window &)> action)2455   void AddAction(const char *label, std::function<void(Window &)> action) {
2456     m_actions.push_back(FormAction(label, action));
2457   }
2458 
2459 protected:
2460   std::vector<FieldDelegateUP> m_fields;
2461   std::vector<FormAction> m_actions;
2462   // Optional error message. If empty, form is considered to have no error.
2463   std::string m_error;
2464 };
2465 
2466 typedef std::shared_ptr<FormDelegate> FormDelegateSP;
2467 
2468 class FormWindowDelegate : public WindowDelegate {
2469 public:
FormWindowDelegate(FormDelegateSP & delegate_sp)2470   FormWindowDelegate(FormDelegateSP &delegate_sp) : m_delegate_sp(delegate_sp) {
2471     assert(m_delegate_sp->GetNumberOfActions() > 0);
2472     if (m_delegate_sp->GetNumberOfFields() > 0)
2473       m_selection_type = SelectionType::Field;
2474     else
2475       m_selection_type = SelectionType::Action;
2476   }
2477 
2478   // Signify which element is selected. If a field or an action is selected,
2479   // then m_selection_index signifies the particular field or action that is
2480   // selected.
2481   enum class SelectionType { Field, Action };
2482 
2483   // A form window is padded by one character from all sides. First, if an error
2484   // message exists, it is drawn followed by a separator. Then one or more
2485   // fields are drawn. Finally, all available actions are drawn on a single
2486   // line.
2487   //
2488   // ___<Form Name>_________________________________________________
2489   // |                                                             |
2490   // | - Error message if it exists.                               |
2491   // |-------------------------------------------------------------|
2492   // | Form elements here.                                         |
2493   // |                       Form actions here.                    |
2494   // |                                                             |
2495   // |______________________________________[Press Esc to cancel]__|
2496   //
2497 
2498   // One line for the error and another for the horizontal line.
GetErrorHeight()2499   int GetErrorHeight() {
2500     if (m_delegate_sp->HasError())
2501       return 2;
2502     return 0;
2503   }
2504 
2505   // Actions span a single line.
GetActionsHeight()2506   int GetActionsHeight() {
2507     if (m_delegate_sp->GetNumberOfActions() > 0)
2508       return 1;
2509     return 0;
2510   }
2511 
2512   // Get the total number of needed lines to draw the contents.
GetContentHeight()2513   int GetContentHeight() {
2514     int height = 0;
2515     height += GetErrorHeight();
2516     for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2517       if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2518         continue;
2519       height += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2520     }
2521     height += GetActionsHeight();
2522     return height;
2523   }
2524 
GetScrollContext()2525   ScrollContext GetScrollContext() {
2526     if (m_selection_type == SelectionType::Action)
2527       return ScrollContext(GetContentHeight() - 1);
2528 
2529     FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2530     ScrollContext context = field->FieldDelegateGetScrollContext();
2531 
2532     int offset = GetErrorHeight();
2533     for (int i = 0; i < m_selection_index; i++) {
2534       if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2535         continue;
2536       offset += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2537     }
2538     context.Offset(offset);
2539 
2540     // If the context is touching the error, include the error in the context as
2541     // well.
2542     if (context.start == GetErrorHeight())
2543       context.start = 0;
2544 
2545     return context;
2546   }
2547 
UpdateScrolling(Surface & surface)2548   void UpdateScrolling(Surface &surface) {
2549     ScrollContext context = GetScrollContext();
2550     int content_height = GetContentHeight();
2551     int surface_height = surface.GetHeight();
2552     int visible_height = std::min(content_height, surface_height);
2553     int last_visible_line = m_first_visible_line + visible_height - 1;
2554 
2555     // If the last visible line is bigger than the content, then it is invalid
2556     // and needs to be set to the last line in the content. This can happen when
2557     // a field has shrunk in height.
2558     if (last_visible_line > content_height - 1) {
2559       m_first_visible_line = content_height - visible_height;
2560     }
2561 
2562     if (context.start < m_first_visible_line) {
2563       m_first_visible_line = context.start;
2564       return;
2565     }
2566 
2567     if (context.end > last_visible_line) {
2568       m_first_visible_line = context.end - visible_height + 1;
2569     }
2570   }
2571 
DrawError(Surface & surface)2572   void DrawError(Surface &surface) {
2573     if (!m_delegate_sp->HasError())
2574       return;
2575     surface.MoveCursor(0, 0);
2576     surface.AttributeOn(COLOR_PAIR(RedOnBlack));
2577     surface.PutChar(ACS_DIAMOND);
2578     surface.PutChar(' ');
2579     surface.PutCStringTruncated(1, m_delegate_sp->GetError().c_str());
2580     surface.AttributeOff(COLOR_PAIR(RedOnBlack));
2581 
2582     surface.MoveCursor(0, 1);
2583     surface.HorizontalLine(surface.GetWidth());
2584   }
2585 
DrawFields(Surface & surface)2586   void DrawFields(Surface &surface) {
2587     int line = 0;
2588     int width = surface.GetWidth();
2589     bool a_field_is_selected = m_selection_type == SelectionType::Field;
2590     for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2591       FieldDelegate *field = m_delegate_sp->GetField(i);
2592       if (!field->FieldDelegateIsVisible())
2593         continue;
2594       bool is_field_selected = a_field_is_selected && m_selection_index == i;
2595       int height = field->FieldDelegateGetHeight();
2596       Rect bounds = Rect(Point(0, line), Size(width, height));
2597       Surface field_surface = surface.SubSurface(bounds);
2598       field->FieldDelegateDraw(field_surface, is_field_selected);
2599       line += height;
2600     }
2601   }
2602 
DrawActions(Surface & surface)2603   void DrawActions(Surface &surface) {
2604     int number_of_actions = m_delegate_sp->GetNumberOfActions();
2605     int width = surface.GetWidth() / number_of_actions;
2606     bool an_action_is_selected = m_selection_type == SelectionType::Action;
2607     int x = 0;
2608     for (int i = 0; i < number_of_actions; i++) {
2609       bool is_action_selected = an_action_is_selected && m_selection_index == i;
2610       FormAction &action = m_delegate_sp->GetAction(i);
2611       Rect bounds = Rect(Point(x, 0), Size(width, 1));
2612       Surface action_surface = surface.SubSurface(bounds);
2613       action.Draw(action_surface, is_action_selected);
2614       x += width;
2615     }
2616   }
2617 
DrawElements(Surface & surface)2618   void DrawElements(Surface &surface) {
2619     Rect frame = surface.GetFrame();
2620     Rect fields_bounds, actions_bounds;
2621     frame.HorizontalSplit(surface.GetHeight() - GetActionsHeight(),
2622                           fields_bounds, actions_bounds);
2623     Surface fields_surface = surface.SubSurface(fields_bounds);
2624     Surface actions_surface = surface.SubSurface(actions_bounds);
2625 
2626     DrawFields(fields_surface);
2627     DrawActions(actions_surface);
2628   }
2629 
2630   // Contents are first drawn on a pad. Then a subset of that pad is copied to
2631   // the derived window starting at the first visible line. This essentially
2632   // provides scrolling functionality.
DrawContent(Surface & surface)2633   void DrawContent(Surface &surface) {
2634     UpdateScrolling(surface);
2635 
2636     int width = surface.GetWidth();
2637     int height = GetContentHeight();
2638     Pad pad = Pad(Size(width, height));
2639 
2640     Rect frame = pad.GetFrame();
2641     Rect error_bounds, elements_bounds;
2642     frame.HorizontalSplit(GetErrorHeight(), error_bounds, elements_bounds);
2643     Surface error_surface = pad.SubSurface(error_bounds);
2644     Surface elements_surface = pad.SubSurface(elements_bounds);
2645 
2646     DrawError(error_surface);
2647     DrawElements(elements_surface);
2648 
2649     int copy_height = std::min(surface.GetHeight(), pad.GetHeight());
2650     pad.CopyToSurface(surface, Point(0, m_first_visible_line), Point(),
2651                       Size(width, copy_height));
2652   }
2653 
DrawSubmitHint(Surface & surface,bool is_active)2654   void DrawSubmitHint(Surface &surface, bool is_active) {
2655     surface.MoveCursor(2, surface.GetHeight() - 1);
2656     if (is_active)
2657       surface.AttributeOn(A_BOLD | COLOR_PAIR(BlackOnWhite));
2658     surface.Printf("[Press Alt+Enter to %s]",
2659                    m_delegate_sp->GetAction(0).GetLabel().c_str());
2660     if (is_active)
2661       surface.AttributeOff(A_BOLD | COLOR_PAIR(BlackOnWhite));
2662   }
2663 
WindowDelegateDraw(Window & window,bool force)2664   bool WindowDelegateDraw(Window &window, bool force) override {
2665     m_delegate_sp->UpdateFieldsVisibility();
2666 
2667     window.Erase();
2668 
2669     window.DrawTitleBox(m_delegate_sp->GetName().c_str(),
2670                         "Press Esc to Cancel");
2671     DrawSubmitHint(window, window.IsActive());
2672 
2673     Rect content_bounds = window.GetFrame();
2674     content_bounds.Inset(2, 2);
2675     Surface content_surface = window.SubSurface(content_bounds);
2676 
2677     DrawContent(content_surface);
2678     return true;
2679   }
2680 
SkipNextHiddenFields()2681   void SkipNextHiddenFields() {
2682     while (true) {
2683       if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2684         return;
2685 
2686       if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2687         m_selection_type = SelectionType::Action;
2688         m_selection_index = 0;
2689         return;
2690       }
2691 
2692       m_selection_index++;
2693     }
2694   }
2695 
SelectNext(int key)2696   HandleCharResult SelectNext(int key) {
2697     if (m_selection_type == SelectionType::Action) {
2698       if (m_selection_index < m_delegate_sp->GetNumberOfActions() - 1) {
2699         m_selection_index++;
2700         return eKeyHandled;
2701       }
2702 
2703       m_selection_index = 0;
2704       m_selection_type = SelectionType::Field;
2705       SkipNextHiddenFields();
2706       if (m_selection_type == SelectionType::Field) {
2707         FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2708         next_field->FieldDelegateSelectFirstElement();
2709       }
2710       return eKeyHandled;
2711     }
2712 
2713     FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2714     if (!field->FieldDelegateOnLastOrOnlyElement()) {
2715       return field->FieldDelegateHandleChar(key);
2716     }
2717 
2718     field->FieldDelegateExitCallback();
2719 
2720     if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2721       m_selection_type = SelectionType::Action;
2722       m_selection_index = 0;
2723       return eKeyHandled;
2724     }
2725 
2726     m_selection_index++;
2727     SkipNextHiddenFields();
2728 
2729     if (m_selection_type == SelectionType::Field) {
2730       FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2731       next_field->FieldDelegateSelectFirstElement();
2732     }
2733 
2734     return eKeyHandled;
2735   }
2736 
SkipPreviousHiddenFields()2737   void SkipPreviousHiddenFields() {
2738     while (true) {
2739       if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2740         return;
2741 
2742       if (m_selection_index == 0) {
2743         m_selection_type = SelectionType::Action;
2744         m_selection_index = 0;
2745         return;
2746       }
2747 
2748       m_selection_index--;
2749     }
2750   }
2751 
SelectPrevious(int key)2752   HandleCharResult SelectPrevious(int key) {
2753     if (m_selection_type == SelectionType::Action) {
2754       if (m_selection_index > 0) {
2755         m_selection_index--;
2756         return eKeyHandled;
2757       }
2758       m_selection_index = m_delegate_sp->GetNumberOfFields() - 1;
2759       m_selection_type = SelectionType::Field;
2760       SkipPreviousHiddenFields();
2761       if (m_selection_type == SelectionType::Field) {
2762         FieldDelegate *previous_field =
2763             m_delegate_sp->GetField(m_selection_index);
2764         previous_field->FieldDelegateSelectLastElement();
2765       }
2766       return eKeyHandled;
2767     }
2768 
2769     FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2770     if (!field->FieldDelegateOnFirstOrOnlyElement()) {
2771       return field->FieldDelegateHandleChar(key);
2772     }
2773 
2774     field->FieldDelegateExitCallback();
2775 
2776     if (m_selection_index == 0) {
2777       m_selection_type = SelectionType::Action;
2778       m_selection_index = m_delegate_sp->GetNumberOfActions() - 1;
2779       return eKeyHandled;
2780     }
2781 
2782     m_selection_index--;
2783     SkipPreviousHiddenFields();
2784 
2785     if (m_selection_type == SelectionType::Field) {
2786       FieldDelegate *previous_field =
2787           m_delegate_sp->GetField(m_selection_index);
2788       previous_field->FieldDelegateSelectLastElement();
2789     }
2790 
2791     return eKeyHandled;
2792   }
2793 
ExecuteAction(Window & window,int index)2794   void ExecuteAction(Window &window, int index) {
2795     FormAction &action = m_delegate_sp->GetAction(index);
2796     action.Execute(window);
2797     if (m_delegate_sp->HasError()) {
2798       m_first_visible_line = 0;
2799       m_selection_index = 0;
2800       m_selection_type = SelectionType::Field;
2801     }
2802   }
2803 
2804   // Always return eKeyHandled to absorb all events since forms are always
2805   // added as pop-ups that should take full control until canceled or submitted.
WindowDelegateHandleChar(Window & window,int key)2806   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
2807     switch (key) {
2808     case '\r':
2809     case '\n':
2810     case KEY_ENTER:
2811       if (m_selection_type == SelectionType::Action) {
2812         ExecuteAction(window, m_selection_index);
2813         return eKeyHandled;
2814       }
2815       break;
2816     case KEY_ALT_ENTER:
2817       ExecuteAction(window, 0);
2818       return eKeyHandled;
2819     case '\t':
2820       SelectNext(key);
2821       return eKeyHandled;
2822     case KEY_SHIFT_TAB:
2823       SelectPrevious(key);
2824       return eKeyHandled;
2825     case KEY_ESCAPE:
2826       window.GetParent()->RemoveSubWindow(&window);
2827       return eKeyHandled;
2828     default:
2829       break;
2830     }
2831 
2832     // If the key wasn't handled and one of the fields is selected, pass the key
2833     // to that field.
2834     if (m_selection_type == SelectionType::Field) {
2835       FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2836       if (field->FieldDelegateHandleChar(key) == eKeyHandled)
2837         return eKeyHandled;
2838     }
2839 
2840     // If the key wasn't handled by the possibly selected field, handle some
2841     // extra keys for navigation.
2842     switch (key) {
2843     case KEY_DOWN:
2844       SelectNext(key);
2845       return eKeyHandled;
2846     case KEY_UP:
2847       SelectPrevious(key);
2848       return eKeyHandled;
2849     default:
2850       break;
2851     }
2852 
2853     return eKeyHandled;
2854   }
2855 
2856 protected:
2857   FormDelegateSP m_delegate_sp;
2858   // The index of the currently selected SelectionType.
2859   int m_selection_index = 0;
2860   // See SelectionType class enum.
2861   SelectionType m_selection_type;
2862   // The first visible line from the pad.
2863   int m_first_visible_line = 0;
2864 };
2865 
2866 ///////////////////////////
2867 // Form Delegate Instances
2868 ///////////////////////////
2869 
2870 class DetachOrKillProcessFormDelegate : public FormDelegate {
2871 public:
DetachOrKillProcessFormDelegate(Process * process)2872   DetachOrKillProcessFormDelegate(Process *process) : m_process(process) {
2873     SetError("There is a running process, either detach or kill it.");
2874 
2875     m_keep_stopped_field =
2876         AddBooleanField("Keep process stopped when detaching.", false);
2877 
2878     AddAction("Detach", [this](Window &window) { Detach(window); });
2879     AddAction("Kill", [this](Window &window) { Kill(window); });
2880   }
2881 
GetName()2882   std::string GetName() override { return "Detach/Kill Process"; }
2883 
Kill(Window & window)2884   void Kill(Window &window) {
2885     Status destroy_status(m_process->Destroy(false));
2886     if (destroy_status.Fail()) {
2887       SetError("Failed to kill process.");
2888       return;
2889     }
2890     window.GetParent()->RemoveSubWindow(&window);
2891   }
2892 
Detach(Window & window)2893   void Detach(Window &window) {
2894     Status detach_status(m_process->Detach(m_keep_stopped_field->GetBoolean()));
2895     if (detach_status.Fail()) {
2896       SetError("Failed to detach from process.");
2897       return;
2898     }
2899     window.GetParent()->RemoveSubWindow(&window);
2900   }
2901 
2902 protected:
2903   Process *m_process;
2904   BooleanFieldDelegate *m_keep_stopped_field;
2905 };
2906 
2907 class ProcessAttachFormDelegate : public FormDelegate {
2908 public:
ProcessAttachFormDelegate(Debugger & debugger,WindowSP main_window_sp)2909   ProcessAttachFormDelegate(Debugger &debugger, WindowSP main_window_sp)
2910       : m_debugger(debugger), m_main_window_sp(main_window_sp) {
2911     std::vector<std::string> types;
2912     types.push_back(std::string("Name"));
2913     types.push_back(std::string("PID"));
2914     m_type_field = AddChoicesField("Attach By", 2, types);
2915     m_pid_field = AddIntegerField("PID", 0, true);
2916     m_name_field =
2917         AddTextField("Process Name", GetDefaultProcessName().c_str(), true);
2918     m_continue_field = AddBooleanField("Continue once attached.", false);
2919     m_wait_for_field = AddBooleanField("Wait for process to launch.", false);
2920     m_include_existing_field =
2921         AddBooleanField("Include existing processes.", false);
2922     m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
2923     m_plugin_field = AddProcessPluginField();
2924 
2925     AddAction("Attach", [this](Window &window) { Attach(window); });
2926   }
2927 
GetName()2928   std::string GetName() override { return "Attach Process"; }
2929 
UpdateFieldsVisibility()2930   void UpdateFieldsVisibility() override {
2931     if (m_type_field->GetChoiceContent() == "Name") {
2932       m_pid_field->FieldDelegateHide();
2933       m_name_field->FieldDelegateShow();
2934       m_wait_for_field->FieldDelegateShow();
2935       if (m_wait_for_field->GetBoolean())
2936         m_include_existing_field->FieldDelegateShow();
2937       else
2938         m_include_existing_field->FieldDelegateHide();
2939     } else {
2940       m_pid_field->FieldDelegateShow();
2941       m_name_field->FieldDelegateHide();
2942       m_wait_for_field->FieldDelegateHide();
2943       m_include_existing_field->FieldDelegateHide();
2944     }
2945     if (m_show_advanced_field->GetBoolean())
2946       m_plugin_field->FieldDelegateShow();
2947     else
2948       m_plugin_field->FieldDelegateHide();
2949   }
2950 
2951   // Get the basename of the target's main executable if available, empty string
2952   // otherwise.
GetDefaultProcessName()2953   std::string GetDefaultProcessName() {
2954     Target *target = m_debugger.GetSelectedTarget().get();
2955     if (target == nullptr)
2956       return "";
2957 
2958     ModuleSP module_sp = target->GetExecutableModule();
2959     if (!module_sp->IsExecutable())
2960       return "";
2961 
2962     return module_sp->GetFileSpec().GetFilename().AsCString();
2963   }
2964 
StopRunningProcess()2965   bool StopRunningProcess() {
2966     ExecutionContext exe_ctx =
2967         m_debugger.GetCommandInterpreter().GetExecutionContext();
2968 
2969     if (!exe_ctx.HasProcessScope())
2970       return false;
2971 
2972     Process *process = exe_ctx.GetProcessPtr();
2973     if (!(process && process->IsAlive()))
2974       return false;
2975 
2976     FormDelegateSP form_delegate_sp =
2977         FormDelegateSP(new DetachOrKillProcessFormDelegate(process));
2978     Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);
2979     WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(
2980         form_delegate_sp->GetName().c_str(), bounds, true);
2981     WindowDelegateSP window_delegate_sp =
2982         WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
2983     form_window_sp->SetDelegate(window_delegate_sp);
2984 
2985     return true;
2986   }
2987 
GetTarget()2988   Target *GetTarget() {
2989     Target *target = m_debugger.GetSelectedTarget().get();
2990 
2991     if (target != nullptr)
2992       return target;
2993 
2994     TargetSP new_target_sp;
2995     m_debugger.GetTargetList().CreateTarget(
2996         m_debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp);
2997 
2998     target = new_target_sp.get();
2999 
3000     if (target == nullptr)
3001       SetError("Failed to create target.");
3002 
3003     m_debugger.GetTargetList().SetSelectedTarget(new_target_sp);
3004 
3005     return target;
3006   }
3007 
GetAttachInfo()3008   ProcessAttachInfo GetAttachInfo() {
3009     ProcessAttachInfo attach_info;
3010     attach_info.SetContinueOnceAttached(m_continue_field->GetBoolean());
3011     if (m_type_field->GetChoiceContent() == "Name") {
3012       attach_info.GetExecutableFile().SetFile(m_name_field->GetText(),
3013                                               FileSpec::Style::native);
3014       attach_info.SetWaitForLaunch(m_wait_for_field->GetBoolean());
3015       if (m_wait_for_field->GetBoolean())
3016         attach_info.SetIgnoreExisting(!m_include_existing_field->GetBoolean());
3017     } else {
3018       attach_info.SetProcessID(m_pid_field->GetInteger());
3019     }
3020     attach_info.SetProcessPluginName(m_plugin_field->GetPluginName());
3021 
3022     return attach_info;
3023   }
3024 
Attach(Window & window)3025   void Attach(Window &window) {
3026     ClearError();
3027 
3028     bool all_fields_are_valid = CheckFieldsValidity();
3029     if (!all_fields_are_valid)
3030       return;
3031 
3032     bool process_is_running = StopRunningProcess();
3033     if (process_is_running)
3034       return;
3035 
3036     Target *target = GetTarget();
3037     if (HasError())
3038       return;
3039 
3040     StreamString stream;
3041     ProcessAttachInfo attach_info = GetAttachInfo();
3042     Status status = target->Attach(attach_info, &stream);
3043 
3044     if (status.Fail()) {
3045       SetError(status.AsCString());
3046       return;
3047     }
3048 
3049     ProcessSP process_sp(target->GetProcessSP());
3050     if (!process_sp) {
3051       SetError("Attached sucessfully but target has no process.");
3052       return;
3053     }
3054 
3055     if (attach_info.GetContinueOnceAttached())
3056       process_sp->Resume();
3057 
3058     window.GetParent()->RemoveSubWindow(&window);
3059   }
3060 
3061 protected:
3062   Debugger &m_debugger;
3063   WindowSP m_main_window_sp;
3064 
3065   ChoicesFieldDelegate *m_type_field;
3066   IntegerFieldDelegate *m_pid_field;
3067   TextFieldDelegate *m_name_field;
3068   BooleanFieldDelegate *m_continue_field;
3069   BooleanFieldDelegate *m_wait_for_field;
3070   BooleanFieldDelegate *m_include_existing_field;
3071   BooleanFieldDelegate *m_show_advanced_field;
3072   ProcessPluginFieldDelegate *m_plugin_field;
3073 };
3074 
3075 class TargetCreateFormDelegate : public FormDelegate {
3076 public:
TargetCreateFormDelegate(Debugger & debugger)3077   TargetCreateFormDelegate(Debugger &debugger) : m_debugger(debugger) {
3078     m_executable_field = AddFileField("Executable", "", /*need_to_exist=*/true,
3079                                       /*required=*/true);
3080     m_core_file_field = AddFileField("Core File", "", /*need_to_exist=*/true,
3081                                      /*required=*/false);
3082     m_symbol_file_field = AddFileField(
3083         "Symbol File", "", /*need_to_exist=*/true, /*required=*/false);
3084     m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
3085     m_remote_file_field = AddFileField(
3086         "Remote File", "", /*need_to_exist=*/false, /*required=*/false);
3087     m_arch_field = AddArchField("Architecture", "", /*required=*/false);
3088     m_platform_field = AddPlatformPluginField(debugger);
3089     m_load_dependent_files_field =
3090         AddChoicesField("Load Dependents", 3, GetLoadDependentFilesChoices());
3091 
3092     AddAction("Create", [this](Window &window) { CreateTarget(window); });
3093   }
3094 
GetName()3095   std::string GetName() override { return "Create Target"; }
3096 
UpdateFieldsVisibility()3097   void UpdateFieldsVisibility() override {
3098     if (m_show_advanced_field->GetBoolean()) {
3099       m_remote_file_field->FieldDelegateShow();
3100       m_arch_field->FieldDelegateShow();
3101       m_platform_field->FieldDelegateShow();
3102       m_load_dependent_files_field->FieldDelegateShow();
3103     } else {
3104       m_remote_file_field->FieldDelegateHide();
3105       m_arch_field->FieldDelegateHide();
3106       m_platform_field->FieldDelegateHide();
3107       m_load_dependent_files_field->FieldDelegateHide();
3108     }
3109   }
3110 
3111   static constexpr const char *kLoadDependentFilesNo = "No";
3112   static constexpr const char *kLoadDependentFilesYes = "Yes";
3113   static constexpr const char *kLoadDependentFilesExecOnly = "Executable only";
3114 
GetLoadDependentFilesChoices()3115   std::vector<std::string> GetLoadDependentFilesChoices() {
3116     std::vector<std::string> load_dependents_options;
3117     load_dependents_options.push_back(kLoadDependentFilesExecOnly);
3118     load_dependents_options.push_back(kLoadDependentFilesYes);
3119     load_dependents_options.push_back(kLoadDependentFilesNo);
3120     return load_dependents_options;
3121   }
3122 
GetLoadDependentFiles()3123   LoadDependentFiles GetLoadDependentFiles() {
3124     std::string choice = m_load_dependent_files_field->GetChoiceContent();
3125     if (choice == kLoadDependentFilesNo)
3126       return eLoadDependentsNo;
3127     if (choice == kLoadDependentFilesYes)
3128       return eLoadDependentsYes;
3129     return eLoadDependentsDefault;
3130   }
3131 
GetPlatformOptions()3132   OptionGroupPlatform GetPlatformOptions() {
3133     OptionGroupPlatform platform_options(false);
3134     platform_options.SetPlatformName(m_platform_field->GetPluginName().c_str());
3135     return platform_options;
3136   }
3137 
GetTarget()3138   TargetSP GetTarget() {
3139     OptionGroupPlatform platform_options = GetPlatformOptions();
3140     TargetSP target_sp;
3141     Status status = m_debugger.GetTargetList().CreateTarget(
3142         m_debugger, m_executable_field->GetPath(),
3143         m_arch_field->GetArchString(), GetLoadDependentFiles(),
3144         &platform_options, target_sp);
3145 
3146     if (status.Fail()) {
3147       SetError(status.AsCString());
3148       return nullptr;
3149     }
3150 
3151     m_debugger.GetTargetList().SetSelectedTarget(target_sp);
3152 
3153     return target_sp;
3154   }
3155 
SetSymbolFile(TargetSP target_sp)3156   void SetSymbolFile(TargetSP target_sp) {
3157     if (!m_symbol_file_field->IsSpecified())
3158       return;
3159 
3160     ModuleSP module_sp(target_sp->GetExecutableModule());
3161     if (!module_sp)
3162       return;
3163 
3164     module_sp->SetSymbolFileFileSpec(
3165         m_symbol_file_field->GetResolvedFileSpec());
3166   }
3167 
SetCoreFile(TargetSP target_sp)3168   void SetCoreFile(TargetSP target_sp) {
3169     if (!m_core_file_field->IsSpecified())
3170       return;
3171 
3172     FileSpec core_file_spec = m_core_file_field->GetResolvedFileSpec();
3173 
3174     FileSpec core_file_directory_spec;
3175     core_file_directory_spec.SetDirectory(core_file_spec.GetDirectory());
3176     target_sp->AppendExecutableSearchPaths(core_file_directory_spec);
3177 
3178     ProcessSP process_sp(target_sp->CreateProcess(
3179         m_debugger.GetListener(), llvm::StringRef(), &core_file_spec, false));
3180 
3181     if (!process_sp) {
3182       SetError("Unable to find process plug-in for core file!");
3183       return;
3184     }
3185 
3186     Status status = process_sp->LoadCore();
3187     if (status.Fail()) {
3188       SetError("Can't find plug-in for core file!");
3189       return;
3190     }
3191   }
3192 
SetRemoteFile(TargetSP target_sp)3193   void SetRemoteFile(TargetSP target_sp) {
3194     if (!m_remote_file_field->IsSpecified())
3195       return;
3196 
3197     ModuleSP module_sp(target_sp->GetExecutableModule());
3198     if (!module_sp)
3199       return;
3200 
3201     FileSpec remote_file_spec = m_remote_file_field->GetFileSpec();
3202     module_sp->SetPlatformFileSpec(remote_file_spec);
3203   }
3204 
RemoveTarget(TargetSP target_sp)3205   void RemoveTarget(TargetSP target_sp) {
3206     m_debugger.GetTargetList().DeleteTarget(target_sp);
3207   }
3208 
CreateTarget(Window & window)3209   void CreateTarget(Window &window) {
3210     ClearError();
3211 
3212     bool all_fields_are_valid = CheckFieldsValidity();
3213     if (!all_fields_are_valid)
3214       return;
3215 
3216     TargetSP target_sp = GetTarget();
3217     if (HasError())
3218       return;
3219 
3220     SetSymbolFile(target_sp);
3221     if (HasError()) {
3222       RemoveTarget(target_sp);
3223       return;
3224     }
3225 
3226     SetCoreFile(target_sp);
3227     if (HasError()) {
3228       RemoveTarget(target_sp);
3229       return;
3230     }
3231 
3232     SetRemoteFile(target_sp);
3233     if (HasError()) {
3234       RemoveTarget(target_sp);
3235       return;
3236     }
3237 
3238     window.GetParent()->RemoveSubWindow(&window);
3239   }
3240 
3241 protected:
3242   Debugger &m_debugger;
3243 
3244   FileFieldDelegate *m_executable_field;
3245   FileFieldDelegate *m_core_file_field;
3246   FileFieldDelegate *m_symbol_file_field;
3247   BooleanFieldDelegate *m_show_advanced_field;
3248   FileFieldDelegate *m_remote_file_field;
3249   ArchFieldDelegate *m_arch_field;
3250   PlatformPluginFieldDelegate *m_platform_field;
3251   ChoicesFieldDelegate *m_load_dependent_files_field;
3252 };
3253 
3254 class ProcessLaunchFormDelegate : public FormDelegate {
3255 public:
ProcessLaunchFormDelegate(Debugger & debugger,WindowSP main_window_sp)3256   ProcessLaunchFormDelegate(Debugger &debugger, WindowSP main_window_sp)
3257       : m_debugger(debugger), m_main_window_sp(main_window_sp) {
3258 
3259     m_arguments_field = AddArgumentsField();
3260     SetArgumentsFieldDefaultValue();
3261     m_target_environment_field =
3262         AddEnvironmentVariableListField("Target Environment Variables");
3263     SetTargetEnvironmentFieldDefaultValue();
3264     m_working_directory_field = AddDirectoryField(
3265         "Working Directory", GetDefaultWorkingDirectory().c_str(), true, false);
3266 
3267     m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
3268 
3269     m_stop_at_entry_field = AddBooleanField("Stop at entry point.", false);
3270     m_detach_on_error_field =
3271         AddBooleanField("Detach on error.", GetDefaultDetachOnError());
3272     m_disable_aslr_field =
3273         AddBooleanField("Disable ASLR", GetDefaultDisableASLR());
3274     m_plugin_field = AddProcessPluginField();
3275     m_arch_field = AddArchField("Architecture", "", false);
3276     m_shell_field = AddFileField("Shell", "", true, false);
3277     m_expand_shell_arguments_field =
3278         AddBooleanField("Expand shell arguments.", false);
3279 
3280     m_disable_standard_io_field =
3281         AddBooleanField("Disable Standard IO", GetDefaultDisableStandardIO());
3282     m_standard_output_field =
3283         AddFileField("Standard Output File", "", /*need_to_exist=*/false,
3284                      /*required=*/false);
3285     m_standard_error_field =
3286         AddFileField("Standard Error File", "", /*need_to_exist=*/false,
3287                      /*required=*/false);
3288     m_standard_input_field =
3289         AddFileField("Standard Input File", "", /*need_to_exist=*/false,
3290                      /*required=*/false);
3291 
3292     m_show_inherited_environment_field =
3293         AddBooleanField("Show inherited environment variables.", false);
3294     m_inherited_environment_field =
3295         AddEnvironmentVariableListField("Inherited Environment Variables");
3296     SetInheritedEnvironmentFieldDefaultValue();
3297 
3298     AddAction("Launch", [this](Window &window) { Launch(window); });
3299   }
3300 
GetName()3301   std::string GetName() override { return "Launch Process"; }
3302 
UpdateFieldsVisibility()3303   void UpdateFieldsVisibility() override {
3304     if (m_show_advanced_field->GetBoolean()) {
3305       m_stop_at_entry_field->FieldDelegateShow();
3306       m_detach_on_error_field->FieldDelegateShow();
3307       m_disable_aslr_field->FieldDelegateShow();
3308       m_plugin_field->FieldDelegateShow();
3309       m_arch_field->FieldDelegateShow();
3310       m_shell_field->FieldDelegateShow();
3311       m_expand_shell_arguments_field->FieldDelegateShow();
3312       m_disable_standard_io_field->FieldDelegateShow();
3313       if (m_disable_standard_io_field->GetBoolean()) {
3314         m_standard_input_field->FieldDelegateHide();
3315         m_standard_output_field->FieldDelegateHide();
3316         m_standard_error_field->FieldDelegateHide();
3317       } else {
3318         m_standard_input_field->FieldDelegateShow();
3319         m_standard_output_field->FieldDelegateShow();
3320         m_standard_error_field->FieldDelegateShow();
3321       }
3322       m_show_inherited_environment_field->FieldDelegateShow();
3323       if (m_show_inherited_environment_field->GetBoolean())
3324         m_inherited_environment_field->FieldDelegateShow();
3325       else
3326         m_inherited_environment_field->FieldDelegateHide();
3327     } else {
3328       m_stop_at_entry_field->FieldDelegateHide();
3329       m_detach_on_error_field->FieldDelegateHide();
3330       m_disable_aslr_field->FieldDelegateHide();
3331       m_plugin_field->FieldDelegateHide();
3332       m_arch_field->FieldDelegateHide();
3333       m_shell_field->FieldDelegateHide();
3334       m_expand_shell_arguments_field->FieldDelegateHide();
3335       m_disable_standard_io_field->FieldDelegateHide();
3336       m_standard_input_field->FieldDelegateHide();
3337       m_standard_output_field->FieldDelegateHide();
3338       m_standard_error_field->FieldDelegateHide();
3339       m_show_inherited_environment_field->FieldDelegateHide();
3340       m_inherited_environment_field->FieldDelegateHide();
3341     }
3342   }
3343 
3344   // Methods for setting the default value of the fields.
3345 
SetArgumentsFieldDefaultValue()3346   void SetArgumentsFieldDefaultValue() {
3347     TargetSP target = m_debugger.GetSelectedTarget();
3348     if (target == nullptr)
3349       return;
3350 
3351     const Args &target_arguments =
3352         target->GetProcessLaunchInfo().GetArguments();
3353     m_arguments_field->AddArguments(target_arguments);
3354   }
3355 
SetTargetEnvironmentFieldDefaultValue()3356   void SetTargetEnvironmentFieldDefaultValue() {
3357     TargetSP target = m_debugger.GetSelectedTarget();
3358     if (target == nullptr)
3359       return;
3360 
3361     const Environment &target_environment = target->GetTargetEnvironment();
3362     m_target_environment_field->AddEnvironmentVariables(target_environment);
3363   }
3364 
SetInheritedEnvironmentFieldDefaultValue()3365   void SetInheritedEnvironmentFieldDefaultValue() {
3366     TargetSP target = m_debugger.GetSelectedTarget();
3367     if (target == nullptr)
3368       return;
3369 
3370     const Environment &inherited_environment =
3371         target->GetInheritedEnvironment();
3372     m_inherited_environment_field->AddEnvironmentVariables(
3373         inherited_environment);
3374   }
3375 
GetDefaultWorkingDirectory()3376   std::string GetDefaultWorkingDirectory() {
3377     TargetSP target = m_debugger.GetSelectedTarget();
3378     if (target == nullptr)
3379       return "";
3380 
3381     PlatformSP platform = target->GetPlatform();
3382     return platform->GetWorkingDirectory().GetPath();
3383   }
3384 
GetDefaultDisableASLR()3385   bool GetDefaultDisableASLR() {
3386     TargetSP target = m_debugger.GetSelectedTarget();
3387     if (target == nullptr)
3388       return false;
3389 
3390     return target->GetDisableASLR();
3391   }
3392 
GetDefaultDisableStandardIO()3393   bool GetDefaultDisableStandardIO() {
3394     TargetSP target = m_debugger.GetSelectedTarget();
3395     if (target == nullptr)
3396       return true;
3397 
3398     return target->GetDisableSTDIO();
3399   }
3400 
GetDefaultDetachOnError()3401   bool GetDefaultDetachOnError() {
3402     TargetSP target = m_debugger.GetSelectedTarget();
3403     if (target == nullptr)
3404       return true;
3405 
3406     return target->GetDetachOnError();
3407   }
3408 
3409   // Methods for getting the necessary information and setting them to the
3410   // ProcessLaunchInfo.
3411 
GetExecutableSettings(ProcessLaunchInfo & launch_info)3412   void GetExecutableSettings(ProcessLaunchInfo &launch_info) {
3413     TargetSP target = m_debugger.GetSelectedTarget();
3414     ModuleSP executable_module = target->GetExecutableModule();
3415     llvm::StringRef target_settings_argv0 = target->GetArg0();
3416 
3417     if (!target_settings_argv0.empty()) {
3418       launch_info.GetArguments().AppendArgument(target_settings_argv0);
3419       launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(),
3420                                     false);
3421       return;
3422     }
3423 
3424     launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(),
3425                                   true);
3426   }
3427 
GetArguments(ProcessLaunchInfo & launch_info)3428   void GetArguments(ProcessLaunchInfo &launch_info) {
3429     TargetSP target = m_debugger.GetSelectedTarget();
3430     Args arguments = m_arguments_field->GetArguments();
3431     launch_info.GetArguments().AppendArguments(arguments);
3432   }
3433 
GetEnvironment(ProcessLaunchInfo & launch_info)3434   void GetEnvironment(ProcessLaunchInfo &launch_info) {
3435     Environment target_environment =
3436         m_target_environment_field->GetEnvironment();
3437     Environment inherited_environment =
3438         m_inherited_environment_field->GetEnvironment();
3439     launch_info.GetEnvironment().insert(target_environment.begin(),
3440                                         target_environment.end());
3441     launch_info.GetEnvironment().insert(inherited_environment.begin(),
3442                                         inherited_environment.end());
3443   }
3444 
GetWorkingDirectory(ProcessLaunchInfo & launch_info)3445   void GetWorkingDirectory(ProcessLaunchInfo &launch_info) {
3446     if (m_working_directory_field->IsSpecified())
3447       launch_info.SetWorkingDirectory(
3448           m_working_directory_field->GetResolvedFileSpec());
3449   }
3450 
GetStopAtEntry(ProcessLaunchInfo & launch_info)3451   void GetStopAtEntry(ProcessLaunchInfo &launch_info) {
3452     if (m_stop_at_entry_field->GetBoolean())
3453       launch_info.GetFlags().Set(eLaunchFlagStopAtEntry);
3454     else
3455       launch_info.GetFlags().Clear(eLaunchFlagStopAtEntry);
3456   }
3457 
GetDetachOnError(ProcessLaunchInfo & launch_info)3458   void GetDetachOnError(ProcessLaunchInfo &launch_info) {
3459     if (m_detach_on_error_field->GetBoolean())
3460       launch_info.GetFlags().Set(eLaunchFlagDetachOnError);
3461     else
3462       launch_info.GetFlags().Clear(eLaunchFlagDetachOnError);
3463   }
3464 
GetDisableASLR(ProcessLaunchInfo & launch_info)3465   void GetDisableASLR(ProcessLaunchInfo &launch_info) {
3466     if (m_disable_aslr_field->GetBoolean())
3467       launch_info.GetFlags().Set(eLaunchFlagDisableASLR);
3468     else
3469       launch_info.GetFlags().Clear(eLaunchFlagDisableASLR);
3470   }
3471 
GetPlugin(ProcessLaunchInfo & launch_info)3472   void GetPlugin(ProcessLaunchInfo &launch_info) {
3473     launch_info.SetProcessPluginName(m_plugin_field->GetPluginName());
3474   }
3475 
GetArch(ProcessLaunchInfo & launch_info)3476   void GetArch(ProcessLaunchInfo &launch_info) {
3477     if (!m_arch_field->IsSpecified())
3478       return;
3479 
3480     TargetSP target_sp = m_debugger.GetSelectedTarget();
3481     PlatformSP platform_sp =
3482         target_sp ? target_sp->GetPlatform() : PlatformSP();
3483     launch_info.GetArchitecture() = Platform::GetAugmentedArchSpec(
3484         platform_sp.get(), m_arch_field->GetArchString());
3485   }
3486 
GetShell(ProcessLaunchInfo & launch_info)3487   void GetShell(ProcessLaunchInfo &launch_info) {
3488     if (!m_shell_field->IsSpecified())
3489       return;
3490 
3491     launch_info.SetShell(m_shell_field->GetResolvedFileSpec());
3492     launch_info.SetShellExpandArguments(
3493         m_expand_shell_arguments_field->GetBoolean());
3494   }
3495 
GetStandardIO(ProcessLaunchInfo & launch_info)3496   void GetStandardIO(ProcessLaunchInfo &launch_info) {
3497     if (m_disable_standard_io_field->GetBoolean()) {
3498       launch_info.GetFlags().Set(eLaunchFlagDisableSTDIO);
3499       return;
3500     }
3501 
3502     FileAction action;
3503     if (m_standard_input_field->IsSpecified()) {
3504       if (action.Open(STDIN_FILENO, m_standard_input_field->GetFileSpec(), true,
3505                       false))
3506         launch_info.AppendFileAction(action);
3507     }
3508     if (m_standard_output_field->IsSpecified()) {
3509       if (action.Open(STDOUT_FILENO, m_standard_output_field->GetFileSpec(),
3510                       false, true))
3511         launch_info.AppendFileAction(action);
3512     }
3513     if (m_standard_error_field->IsSpecified()) {
3514       if (action.Open(STDERR_FILENO, m_standard_error_field->GetFileSpec(),
3515                       false, true))
3516         launch_info.AppendFileAction(action);
3517     }
3518   }
3519 
GetInheritTCC(ProcessLaunchInfo & launch_info)3520   void GetInheritTCC(ProcessLaunchInfo &launch_info) {
3521     if (m_debugger.GetSelectedTarget()->GetInheritTCC())
3522       launch_info.GetFlags().Set(eLaunchFlagInheritTCCFromParent);
3523   }
3524 
GetLaunchInfo()3525   ProcessLaunchInfo GetLaunchInfo() {
3526     ProcessLaunchInfo launch_info;
3527 
3528     GetExecutableSettings(launch_info);
3529     GetArguments(launch_info);
3530     GetEnvironment(launch_info);
3531     GetWorkingDirectory(launch_info);
3532     GetStopAtEntry(launch_info);
3533     GetDetachOnError(launch_info);
3534     GetDisableASLR(launch_info);
3535     GetPlugin(launch_info);
3536     GetArch(launch_info);
3537     GetShell(launch_info);
3538     GetStandardIO(launch_info);
3539     GetInheritTCC(launch_info);
3540 
3541     return launch_info;
3542   }
3543 
StopRunningProcess()3544   bool StopRunningProcess() {
3545     ExecutionContext exe_ctx =
3546         m_debugger.GetCommandInterpreter().GetExecutionContext();
3547 
3548     if (!exe_ctx.HasProcessScope())
3549       return false;
3550 
3551     Process *process = exe_ctx.GetProcessPtr();
3552     if (!(process && process->IsAlive()))
3553       return false;
3554 
3555     FormDelegateSP form_delegate_sp =
3556         FormDelegateSP(new DetachOrKillProcessFormDelegate(process));
3557     Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);
3558     WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(
3559         form_delegate_sp->GetName().c_str(), bounds, true);
3560     WindowDelegateSP window_delegate_sp =
3561         WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
3562     form_window_sp->SetDelegate(window_delegate_sp);
3563 
3564     return true;
3565   }
3566 
GetTarget()3567   Target *GetTarget() {
3568     Target *target = m_debugger.GetSelectedTarget().get();
3569 
3570     if (target == nullptr) {
3571       SetError("No target exists!");
3572       return nullptr;
3573     }
3574 
3575     ModuleSP exe_module_sp = target->GetExecutableModule();
3576 
3577     if (exe_module_sp == nullptr) {
3578       SetError("No executable in target!");
3579       return nullptr;
3580     }
3581 
3582     return target;
3583   }
3584 
Launch(Window & window)3585   void Launch(Window &window) {
3586     ClearError();
3587 
3588     bool all_fields_are_valid = CheckFieldsValidity();
3589     if (!all_fields_are_valid)
3590       return;
3591 
3592     bool process_is_running = StopRunningProcess();
3593     if (process_is_running)
3594       return;
3595 
3596     Target *target = GetTarget();
3597     if (HasError())
3598       return;
3599 
3600     StreamString stream;
3601     ProcessLaunchInfo launch_info = GetLaunchInfo();
3602     Status status = target->Launch(launch_info, &stream);
3603 
3604     if (status.Fail()) {
3605       SetError(status.AsCString());
3606       return;
3607     }
3608 
3609     ProcessSP process_sp(target->GetProcessSP());
3610     if (!process_sp) {
3611       SetError("Launched successfully but target has no process!");
3612       return;
3613     }
3614 
3615     window.GetParent()->RemoveSubWindow(&window);
3616   }
3617 
3618 protected:
3619   Debugger &m_debugger;
3620   WindowSP m_main_window_sp;
3621 
3622   ArgumentsFieldDelegate *m_arguments_field;
3623   EnvironmentVariableListFieldDelegate *m_target_environment_field;
3624   DirectoryFieldDelegate *m_working_directory_field;
3625 
3626   BooleanFieldDelegate *m_show_advanced_field;
3627 
3628   BooleanFieldDelegate *m_stop_at_entry_field;
3629   BooleanFieldDelegate *m_detach_on_error_field;
3630   BooleanFieldDelegate *m_disable_aslr_field;
3631   ProcessPluginFieldDelegate *m_plugin_field;
3632   ArchFieldDelegate *m_arch_field;
3633   FileFieldDelegate *m_shell_field;
3634   BooleanFieldDelegate *m_expand_shell_arguments_field;
3635   BooleanFieldDelegate *m_disable_standard_io_field;
3636   FileFieldDelegate *m_standard_input_field;
3637   FileFieldDelegate *m_standard_output_field;
3638   FileFieldDelegate *m_standard_error_field;
3639 
3640   BooleanFieldDelegate *m_show_inherited_environment_field;
3641   EnvironmentVariableListFieldDelegate *m_inherited_environment_field;
3642 };
3643 
3644 ////////////
3645 // Searchers
3646 ////////////
3647 
3648 class SearcherDelegate {
3649 public:
3650   SearcherDelegate() = default;
3651 
3652   virtual ~SearcherDelegate() = default;
3653 
3654   virtual int GetNumberOfMatches() = 0;
3655 
3656   // Get the string that will be displayed for the match at the input index.
3657   virtual const std::string &GetMatchTextAtIndex(int index) = 0;
3658 
3659   // Update the matches of the search. This is executed every time the text
3660   // field handles an event.
3661   virtual void UpdateMatches(const std::string &text) = 0;
3662 
3663   // Execute the user callback given the index of some match. This is executed
3664   // once the user selects a match.
3665   virtual void ExecuteCallback(int match_index) = 0;
3666 };
3667 
3668 typedef std::shared_ptr<SearcherDelegate> SearcherDelegateSP;
3669 
3670 class SearcherWindowDelegate : public WindowDelegate {
3671 public:
SearcherWindowDelegate(SearcherDelegateSP & delegate_sp)3672   SearcherWindowDelegate(SearcherDelegateSP &delegate_sp)
3673       : m_delegate_sp(delegate_sp), m_text_field("Search", "", false) {
3674     ;
3675   }
3676 
3677   // A completion window is padded by one character from all sides. A text field
3678   // is first drawn for inputting the searcher request, then a list of matches
3679   // are displayed in a scrollable list.
3680   //
3681   // ___<Searcher Window Name>____________________________
3682   // |                                                   |
3683   // | __[Search]_______________________________________ |
3684   // | |                                               | |
3685   // | |_______________________________________________| |
3686   // | - Match 1.                                        |
3687   // | - Match 2.                                        |
3688   // | - ...                                             |
3689   // |                                                   |
3690   // |____________________________[Press Esc to Cancel]__|
3691   //
3692 
3693   // Get the index of the last visible match. Assuming at least one match
3694   // exists.
GetLastVisibleMatch(int height)3695   int GetLastVisibleMatch(int height) {
3696     int index = m_first_visible_match + height;
3697     return std::min(index, m_delegate_sp->GetNumberOfMatches()) - 1;
3698   }
3699 
GetNumberOfVisibleMatches(int height)3700   int GetNumberOfVisibleMatches(int height) {
3701     return GetLastVisibleMatch(height) - m_first_visible_match + 1;
3702   }
3703 
UpdateScrolling(Surface & surface)3704   void UpdateScrolling(Surface &surface) {
3705     if (m_selected_match < m_first_visible_match) {
3706       m_first_visible_match = m_selected_match;
3707       return;
3708     }
3709 
3710     int height = surface.GetHeight();
3711     int last_visible_match = GetLastVisibleMatch(height);
3712     if (m_selected_match > last_visible_match) {
3713       m_first_visible_match = m_selected_match - height + 1;
3714     }
3715   }
3716 
DrawMatches(Surface & surface)3717   void DrawMatches(Surface &surface) {
3718     if (m_delegate_sp->GetNumberOfMatches() == 0)
3719       return;
3720 
3721     UpdateScrolling(surface);
3722 
3723     int count = GetNumberOfVisibleMatches(surface.GetHeight());
3724     for (int i = 0; i < count; i++) {
3725       surface.MoveCursor(1, i);
3726       int current_match = m_first_visible_match + i;
3727       if (current_match == m_selected_match)
3728         surface.AttributeOn(A_REVERSE);
3729       surface.PutCString(
3730           m_delegate_sp->GetMatchTextAtIndex(current_match).c_str());
3731       if (current_match == m_selected_match)
3732         surface.AttributeOff(A_REVERSE);
3733     }
3734   }
3735 
DrawContent(Surface & surface)3736   void DrawContent(Surface &surface) {
3737     Rect content_bounds = surface.GetFrame();
3738     Rect text_field_bounds, matchs_bounds;
3739     content_bounds.HorizontalSplit(m_text_field.FieldDelegateGetHeight(),
3740                                    text_field_bounds, matchs_bounds);
3741     Surface text_field_surface = surface.SubSurface(text_field_bounds);
3742     Surface matches_surface = surface.SubSurface(matchs_bounds);
3743 
3744     m_text_field.FieldDelegateDraw(text_field_surface, true);
3745     DrawMatches(matches_surface);
3746   }
3747 
WindowDelegateDraw(Window & window,bool force)3748   bool WindowDelegateDraw(Window &window, bool force) override {
3749     window.Erase();
3750 
3751     window.DrawTitleBox(window.GetName(), "Press Esc to Cancel");
3752 
3753     Rect content_bounds = window.GetFrame();
3754     content_bounds.Inset(2, 2);
3755     Surface content_surface = window.SubSurface(content_bounds);
3756 
3757     DrawContent(content_surface);
3758     return true;
3759   }
3760 
SelectNext()3761   void SelectNext() {
3762     if (m_selected_match != m_delegate_sp->GetNumberOfMatches() - 1)
3763       m_selected_match++;
3764   }
3765 
SelectPrevious()3766   void SelectPrevious() {
3767     if (m_selected_match != 0)
3768       m_selected_match--;
3769   }
3770 
ExecuteCallback(Window & window)3771   void ExecuteCallback(Window &window) {
3772     m_delegate_sp->ExecuteCallback(m_selected_match);
3773     window.GetParent()->RemoveSubWindow(&window);
3774   }
3775 
UpdateMatches()3776   void UpdateMatches() {
3777     m_delegate_sp->UpdateMatches(m_text_field.GetText());
3778     m_selected_match = 0;
3779   }
3780 
WindowDelegateHandleChar(Window & window,int key)3781   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
3782     switch (key) {
3783     case '\r':
3784     case '\n':
3785     case KEY_ENTER:
3786       ExecuteCallback(window);
3787       return eKeyHandled;
3788     case '\t':
3789     case KEY_DOWN:
3790       SelectNext();
3791       return eKeyHandled;
3792     case KEY_SHIFT_TAB:
3793     case KEY_UP:
3794       SelectPrevious();
3795       return eKeyHandled;
3796     case KEY_ESCAPE:
3797       window.GetParent()->RemoveSubWindow(&window);
3798       return eKeyHandled;
3799     default:
3800       break;
3801     }
3802 
3803     if (m_text_field.FieldDelegateHandleChar(key) == eKeyHandled)
3804       UpdateMatches();
3805 
3806     return eKeyHandled;
3807   }
3808 
3809 protected:
3810   SearcherDelegateSP m_delegate_sp;
3811   TextFieldDelegate m_text_field;
3812   // The index of the currently selected match.
3813   int m_selected_match = 0;
3814   // The index of the first visible match.
3815   int m_first_visible_match = 0;
3816 };
3817 
3818 //////////////////////////////
3819 // Searcher Delegate Instances
3820 //////////////////////////////
3821 
3822 // This is a searcher delegate wrapper around CommandCompletions common
3823 // callbacks. The callbacks are only given the match string. The completion_mask
3824 // can be a combination of CommonCompletionTypes.
3825 class CommonCompletionSearcherDelegate : public SearcherDelegate {
3826 public:
3827   typedef std::function<void(const std::string &)> CallbackType;
3828 
CommonCompletionSearcherDelegate(Debugger & debugger,uint32_t completion_mask,CallbackType callback)3829   CommonCompletionSearcherDelegate(Debugger &debugger, uint32_t completion_mask,
3830                                    CallbackType callback)
3831       : m_debugger(debugger), m_completion_mask(completion_mask),
3832         m_callback(callback) {}
3833 
GetNumberOfMatches()3834   int GetNumberOfMatches() override { return m_matches.GetSize(); }
3835 
GetMatchTextAtIndex(int index)3836   const std::string &GetMatchTextAtIndex(int index) override {
3837     return m_matches[index];
3838   }
3839 
UpdateMatches(const std::string & text)3840   void UpdateMatches(const std::string &text) override {
3841     CompletionResult result;
3842     CompletionRequest request(text.c_str(), text.size(), result);
3843     CommandCompletions::InvokeCommonCompletionCallbacks(
3844         m_debugger.GetCommandInterpreter(), m_completion_mask, request,
3845         nullptr);
3846     result.GetMatches(m_matches);
3847   }
3848 
ExecuteCallback(int match_index)3849   void ExecuteCallback(int match_index) override {
3850     m_callback(m_matches[match_index]);
3851   }
3852 
3853 protected:
3854   Debugger &m_debugger;
3855   // A compound mask from CommonCompletionTypes.
3856   uint32_t m_completion_mask;
3857   // A callback to execute once the user selects a match. The match is passed to
3858   // the callback as a string.
3859   CallbackType m_callback;
3860   StringList m_matches;
3861 };
3862 
3863 ////////
3864 // Menus
3865 ////////
3866 
3867 class MenuDelegate {
3868 public:
3869   virtual ~MenuDelegate() = default;
3870 
3871   virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0;
3872 };
3873 
3874 class Menu : public WindowDelegate {
3875 public:
3876   enum class Type { Invalid, Bar, Item, Separator };
3877 
3878   // Menubar or separator constructor
3879   Menu(Type type);
3880 
3881   // Menuitem constructor
3882   Menu(const char *name, const char *key_name, int key_value,
3883        uint64_t identifier);
3884 
3885   ~Menu() override = default;
3886 
GetDelegate() const3887   const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; }
3888 
SetDelegate(const MenuDelegateSP & delegate_sp)3889   void SetDelegate(const MenuDelegateSP &delegate_sp) {
3890     m_delegate_sp = delegate_sp;
3891   }
3892 
3893   void RecalculateNameLengths();
3894 
3895   void AddSubmenu(const MenuSP &menu_sp);
3896 
3897   int DrawAndRunMenu(Window &window);
3898 
3899   void DrawMenuTitle(Window &window, bool highlight);
3900 
3901   bool WindowDelegateDraw(Window &window, bool force) override;
3902 
3903   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
3904 
ActionPrivate(Menu & menu)3905   MenuActionResult ActionPrivate(Menu &menu) {
3906     MenuActionResult result = MenuActionResult::NotHandled;
3907     if (m_delegate_sp) {
3908       result = m_delegate_sp->MenuDelegateAction(menu);
3909       if (result != MenuActionResult::NotHandled)
3910         return result;
3911     } else if (m_parent) {
3912       result = m_parent->ActionPrivate(menu);
3913       if (result != MenuActionResult::NotHandled)
3914         return result;
3915     }
3916     return m_canned_result;
3917   }
3918 
Action()3919   MenuActionResult Action() {
3920     // Call the recursive action so it can try to handle it with the menu
3921     // delegate, and if not, try our parent menu
3922     return ActionPrivate(*this);
3923   }
3924 
SetCannedResult(MenuActionResult result)3925   void SetCannedResult(MenuActionResult result) { m_canned_result = result; }
3926 
GetSubmenus()3927   Menus &GetSubmenus() { return m_submenus; }
3928 
GetSubmenus() const3929   const Menus &GetSubmenus() const { return m_submenus; }
3930 
GetSelectedSubmenuIndex() const3931   int GetSelectedSubmenuIndex() const { return m_selected; }
3932 
SetSelectedSubmenuIndex(int idx)3933   void SetSelectedSubmenuIndex(int idx) { m_selected = idx; }
3934 
GetType() const3935   Type GetType() const { return m_type; }
3936 
GetStartingColumn() const3937   int GetStartingColumn() const { return m_start_col; }
3938 
SetStartingColumn(int col)3939   void SetStartingColumn(int col) { m_start_col = col; }
3940 
GetKeyValue() const3941   int GetKeyValue() const { return m_key_value; }
3942 
GetName()3943   std::string &GetName() { return m_name; }
3944 
GetDrawWidth() const3945   int GetDrawWidth() const {
3946     return m_max_submenu_name_length + m_max_submenu_key_name_length + 8;
3947   }
3948 
GetIdentifier() const3949   uint64_t GetIdentifier() const { return m_identifier; }
3950 
SetIdentifier(uint64_t identifier)3951   void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
3952 
3953 protected:
3954   std::string m_name;
3955   std::string m_key_name;
3956   uint64_t m_identifier;
3957   Type m_type;
3958   int m_key_value;
3959   int m_start_col;
3960   int m_max_submenu_name_length;
3961   int m_max_submenu_key_name_length;
3962   int m_selected;
3963   Menu *m_parent;
3964   Menus m_submenus;
3965   WindowSP m_menu_window_sp;
3966   MenuActionResult m_canned_result;
3967   MenuDelegateSP m_delegate_sp;
3968 };
3969 
3970 // Menubar or separator constructor
Menu(Type type)3971 Menu::Menu(Type type)
3972     : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0),
3973       m_start_col(0), m_max_submenu_name_length(0),
3974       m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
3975       m_submenus(), m_canned_result(MenuActionResult::NotHandled),
3976       m_delegate_sp() {}
3977 
3978 // Menuitem constructor
Menu(const char * name,const char * key_name,int key_value,uint64_t identifier)3979 Menu::Menu(const char *name, const char *key_name, int key_value,
3980            uint64_t identifier)
3981     : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid),
3982       m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0),
3983       m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
3984       m_submenus(), m_canned_result(MenuActionResult::NotHandled),
3985       m_delegate_sp() {
3986   if (name && name[0]) {
3987     m_name = name;
3988     m_type = Type::Item;
3989     if (key_name && key_name[0])
3990       m_key_name = key_name;
3991   } else {
3992     m_type = Type::Separator;
3993   }
3994 }
3995 
RecalculateNameLengths()3996 void Menu::RecalculateNameLengths() {
3997   m_max_submenu_name_length = 0;
3998   m_max_submenu_key_name_length = 0;
3999   Menus &submenus = GetSubmenus();
4000   const size_t num_submenus = submenus.size();
4001   for (size_t i = 0; i < num_submenus; ++i) {
4002     Menu *submenu = submenus[i].get();
4003     if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size())
4004       m_max_submenu_name_length = submenu->m_name.size();
4005     if (static_cast<size_t>(m_max_submenu_key_name_length) <
4006         submenu->m_key_name.size())
4007       m_max_submenu_key_name_length = submenu->m_key_name.size();
4008   }
4009 }
4010 
AddSubmenu(const MenuSP & menu_sp)4011 void Menu::AddSubmenu(const MenuSP &menu_sp) {
4012   menu_sp->m_parent = this;
4013   if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size())
4014     m_max_submenu_name_length = menu_sp->m_name.size();
4015   if (static_cast<size_t>(m_max_submenu_key_name_length) <
4016       menu_sp->m_key_name.size())
4017     m_max_submenu_key_name_length = menu_sp->m_key_name.size();
4018   m_submenus.push_back(menu_sp);
4019 }
4020 
DrawMenuTitle(Window & window,bool highlight)4021 void Menu::DrawMenuTitle(Window &window, bool highlight) {
4022   if (m_type == Type::Separator) {
4023     window.MoveCursor(0, window.GetCursorY());
4024     window.PutChar(ACS_LTEE);
4025     int width = window.GetWidth();
4026     if (width > 2) {
4027       width -= 2;
4028       for (int i = 0; i < width; ++i)
4029         window.PutChar(ACS_HLINE);
4030     }
4031     window.PutChar(ACS_RTEE);
4032   } else {
4033     const int shortcut_key = m_key_value;
4034     bool underlined_shortcut = false;
4035     const attr_t highlight_attr = A_REVERSE;
4036     if (highlight)
4037       window.AttributeOn(highlight_attr);
4038     if (llvm::isPrint(shortcut_key)) {
4039       size_t lower_pos = m_name.find(tolower(shortcut_key));
4040       size_t upper_pos = m_name.find(toupper(shortcut_key));
4041       const char *name = m_name.c_str();
4042       size_t pos = std::min<size_t>(lower_pos, upper_pos);
4043       if (pos != std::string::npos) {
4044         underlined_shortcut = true;
4045         if (pos > 0) {
4046           window.PutCString(name, pos);
4047           name += pos;
4048         }
4049         const attr_t shortcut_attr = A_UNDERLINE | A_BOLD;
4050         window.AttributeOn(shortcut_attr);
4051         window.PutChar(name[0]);
4052         window.AttributeOff(shortcut_attr);
4053         name++;
4054         if (name[0])
4055           window.PutCString(name);
4056       }
4057     }
4058 
4059     if (!underlined_shortcut) {
4060       window.PutCString(m_name.c_str());
4061     }
4062 
4063     if (highlight)
4064       window.AttributeOff(highlight_attr);
4065 
4066     if (m_key_name.empty()) {
4067       if (!underlined_shortcut && llvm::isPrint(m_key_value)) {
4068         window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
4069         window.Printf(" (%c)", m_key_value);
4070         window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
4071       }
4072     } else {
4073       window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
4074       window.Printf(" (%s)", m_key_name.c_str());
4075       window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
4076     }
4077   }
4078 }
4079 
WindowDelegateDraw(Window & window,bool force)4080 bool Menu::WindowDelegateDraw(Window &window, bool force) {
4081   Menus &submenus = GetSubmenus();
4082   const size_t num_submenus = submenus.size();
4083   const int selected_idx = GetSelectedSubmenuIndex();
4084   Menu::Type menu_type = GetType();
4085   switch (menu_type) {
4086   case Menu::Type::Bar: {
4087     window.SetBackground(BlackOnWhite);
4088     window.MoveCursor(0, 0);
4089     for (size_t i = 0; i < num_submenus; ++i) {
4090       Menu *menu = submenus[i].get();
4091       if (i > 0)
4092         window.PutChar(' ');
4093       menu->SetStartingColumn(window.GetCursorX());
4094       window.PutCString("| ");
4095       menu->DrawMenuTitle(window, false);
4096     }
4097     window.PutCString(" |");
4098   } break;
4099 
4100   case Menu::Type::Item: {
4101     int y = 1;
4102     int x = 3;
4103     // Draw the menu
4104     int cursor_x = 0;
4105     int cursor_y = 0;
4106     window.Erase();
4107     window.SetBackground(BlackOnWhite);
4108     window.Box();
4109     for (size_t i = 0; i < num_submenus; ++i) {
4110       const bool is_selected = (i == static_cast<size_t>(selected_idx));
4111       window.MoveCursor(x, y + i);
4112       if (is_selected) {
4113         // Remember where we want the cursor to be
4114         cursor_x = x - 1;
4115         cursor_y = y + i;
4116       }
4117       submenus[i]->DrawMenuTitle(window, is_selected);
4118     }
4119     window.MoveCursor(cursor_x, cursor_y);
4120   } break;
4121 
4122   default:
4123   case Menu::Type::Separator:
4124     break;
4125   }
4126   return true; // Drawing handled...
4127 }
4128 
WindowDelegateHandleChar(Window & window,int key)4129 HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) {
4130   HandleCharResult result = eKeyNotHandled;
4131 
4132   Menus &submenus = GetSubmenus();
4133   const size_t num_submenus = submenus.size();
4134   const int selected_idx = GetSelectedSubmenuIndex();
4135   Menu::Type menu_type = GetType();
4136   if (menu_type == Menu::Type::Bar) {
4137     MenuSP run_menu_sp;
4138     switch (key) {
4139     case KEY_DOWN:
4140     case KEY_UP:
4141       // Show last menu or first menu
4142       if (selected_idx < static_cast<int>(num_submenus))
4143         run_menu_sp = submenus[selected_idx];
4144       else if (!submenus.empty())
4145         run_menu_sp = submenus.front();
4146       result = eKeyHandled;
4147       break;
4148 
4149     case KEY_RIGHT:
4150       ++m_selected;
4151       if (m_selected >= static_cast<int>(num_submenus))
4152         m_selected = 0;
4153       if (m_selected < static_cast<int>(num_submenus))
4154         run_menu_sp = submenus[m_selected];
4155       else if (!submenus.empty())
4156         run_menu_sp = submenus.front();
4157       result = eKeyHandled;
4158       break;
4159 
4160     case KEY_LEFT:
4161       --m_selected;
4162       if (m_selected < 0)
4163         m_selected = num_submenus - 1;
4164       if (m_selected < static_cast<int>(num_submenus))
4165         run_menu_sp = submenus[m_selected];
4166       else if (!submenus.empty())
4167         run_menu_sp = submenus.front();
4168       result = eKeyHandled;
4169       break;
4170 
4171     default:
4172       for (size_t i = 0; i < num_submenus; ++i) {
4173         if (submenus[i]->GetKeyValue() == key) {
4174           SetSelectedSubmenuIndex(i);
4175           run_menu_sp = submenus[i];
4176           result = eKeyHandled;
4177           break;
4178         }
4179       }
4180       break;
4181     }
4182 
4183     if (run_menu_sp) {
4184       // Run the action on this menu in case we need to populate the menu with
4185       // dynamic content and also in case check marks, and any other menu
4186       // decorations need to be calculated
4187       if (run_menu_sp->Action() == MenuActionResult::Quit)
4188         return eQuitApplication;
4189 
4190       Rect menu_bounds;
4191       menu_bounds.origin.x = run_menu_sp->GetStartingColumn();
4192       menu_bounds.origin.y = 1;
4193       menu_bounds.size.width = run_menu_sp->GetDrawWidth();
4194       menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2;
4195       if (m_menu_window_sp)
4196         window.GetParent()->RemoveSubWindow(m_menu_window_sp.get());
4197 
4198       m_menu_window_sp = window.GetParent()->CreateSubWindow(
4199           run_menu_sp->GetName().c_str(), menu_bounds, true);
4200       m_menu_window_sp->SetDelegate(run_menu_sp);
4201     }
4202   } else if (menu_type == Menu::Type::Item) {
4203     switch (key) {
4204     case KEY_DOWN:
4205       if (m_submenus.size() > 1) {
4206         const int start_select = m_selected;
4207         while (++m_selected != start_select) {
4208           if (static_cast<size_t>(m_selected) >= num_submenus)
4209             m_selected = 0;
4210           if (m_submenus[m_selected]->GetType() == Type::Separator)
4211             continue;
4212           else
4213             break;
4214         }
4215         return eKeyHandled;
4216       }
4217       break;
4218 
4219     case KEY_UP:
4220       if (m_submenus.size() > 1) {
4221         const int start_select = m_selected;
4222         while (--m_selected != start_select) {
4223           if (m_selected < static_cast<int>(0))
4224             m_selected = num_submenus - 1;
4225           if (m_submenus[m_selected]->GetType() == Type::Separator)
4226             continue;
4227           else
4228             break;
4229         }
4230         return eKeyHandled;
4231       }
4232       break;
4233 
4234     case KEY_RETURN:
4235       if (static_cast<size_t>(selected_idx) < num_submenus) {
4236         if (submenus[selected_idx]->Action() == MenuActionResult::Quit)
4237           return eQuitApplication;
4238         window.GetParent()->RemoveSubWindow(&window);
4239         return eKeyHandled;
4240       }
4241       break;
4242 
4243     case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in
4244                      // case other chars are entered for escaped sequences
4245       window.GetParent()->RemoveSubWindow(&window);
4246       return eKeyHandled;
4247 
4248     default:
4249       for (size_t i = 0; i < num_submenus; ++i) {
4250         Menu *menu = submenus[i].get();
4251         if (menu->GetKeyValue() == key) {
4252           SetSelectedSubmenuIndex(i);
4253           window.GetParent()->RemoveSubWindow(&window);
4254           if (menu->Action() == MenuActionResult::Quit)
4255             return eQuitApplication;
4256           return eKeyHandled;
4257         }
4258       }
4259       break;
4260     }
4261   } else if (menu_type == Menu::Type::Separator) {
4262   }
4263   return result;
4264 }
4265 
4266 class Application {
4267 public:
Application(FILE * in,FILE * out)4268   Application(FILE *in, FILE *out) : m_window_sp(), m_in(in), m_out(out) {}
4269 
~Application()4270   ~Application() {
4271     m_window_delegates.clear();
4272     m_window_sp.reset();
4273     if (m_screen) {
4274       ::delscreen(m_screen);
4275       m_screen = nullptr;
4276     }
4277   }
4278 
Initialize()4279   void Initialize() {
4280     m_screen = ::newterm(nullptr, m_out, m_in);
4281     ::start_color();
4282     ::curs_set(0);
4283     ::noecho();
4284     ::keypad(stdscr, TRUE);
4285   }
4286 
Terminate()4287   void Terminate() { ::endwin(); }
4288 
Run(Debugger & debugger)4289   void Run(Debugger &debugger) {
4290     bool done = false;
4291     int delay_in_tenths_of_a_second = 1;
4292 
4293     // Alas the threading model in curses is a bit lame so we need to resort
4294     // to polling every 0.5 seconds. We could poll for stdin ourselves and
4295     // then pass the keys down but then we need to translate all of the escape
4296     // sequences ourselves. So we resort to polling for input because we need
4297     // to receive async process events while in this loop.
4298 
4299     halfdelay(delay_in_tenths_of_a_second); // Poll using some number of
4300                                             // tenths of seconds seconds when
4301                                             // calling Window::GetChar()
4302 
4303     ListenerSP listener_sp(
4304         Listener::MakeListener("lldb.IOHandler.curses.Application"));
4305     ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass());
4306     debugger.EnableForwardEvents(listener_sp);
4307 
4308     m_update_screen = true;
4309 #if defined(__APPLE__)
4310     std::deque<int> escape_chars;
4311 #endif
4312 
4313     while (!done) {
4314       if (m_update_screen) {
4315         m_window_sp->Draw(false);
4316         // All windows should be calling Window::DeferredRefresh() instead of
4317         // Window::Refresh() so we can do a single update and avoid any screen
4318         // blinking
4319         update_panels();
4320 
4321         // Cursor hiding isn't working on MacOSX, so hide it in the top left
4322         // corner
4323         m_window_sp->MoveCursor(0, 0);
4324 
4325         doupdate();
4326         m_update_screen = false;
4327       }
4328 
4329 #if defined(__APPLE__)
4330       // Terminal.app doesn't map its function keys correctly, F1-F4 default
4331       // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if
4332       // possible
4333       int ch;
4334       if (escape_chars.empty())
4335         ch = m_window_sp->GetChar();
4336       else {
4337         ch = escape_chars.front();
4338         escape_chars.pop_front();
4339       }
4340       if (ch == KEY_ESCAPE) {
4341         int ch2 = m_window_sp->GetChar();
4342         if (ch2 == 'O') {
4343           int ch3 = m_window_sp->GetChar();
4344           switch (ch3) {
4345           case 'P':
4346             ch = KEY_F(1);
4347             break;
4348           case 'Q':
4349             ch = KEY_F(2);
4350             break;
4351           case 'R':
4352             ch = KEY_F(3);
4353             break;
4354           case 'S':
4355             ch = KEY_F(4);
4356             break;
4357           default:
4358             escape_chars.push_back(ch2);
4359             if (ch3 != -1)
4360               escape_chars.push_back(ch3);
4361             break;
4362           }
4363         } else if (ch2 != -1)
4364           escape_chars.push_back(ch2);
4365       }
4366 #else
4367       int ch = m_window_sp->GetChar();
4368 
4369 #endif
4370       if (ch == -1) {
4371         if (feof(m_in) || ferror(m_in)) {
4372           done = true;
4373         } else {
4374           // Just a timeout from using halfdelay(), check for events
4375           EventSP event_sp;
4376           while (listener_sp->PeekAtNextEvent()) {
4377             listener_sp->GetEvent(event_sp, std::chrono::seconds(0));
4378 
4379             if (event_sp) {
4380               Broadcaster *broadcaster = event_sp->GetBroadcaster();
4381               if (broadcaster) {
4382                 // uint32_t event_type = event_sp->GetType();
4383                 ConstString broadcaster_class(
4384                     broadcaster->GetBroadcasterClass());
4385                 if (broadcaster_class == broadcaster_class_process) {
4386                   m_update_screen = true;
4387                   continue; // Don't get any key, just update our view
4388                 }
4389               }
4390             }
4391           }
4392         }
4393       } else {
4394         HandleCharResult key_result = m_window_sp->HandleChar(ch);
4395         switch (key_result) {
4396         case eKeyHandled:
4397           m_update_screen = true;
4398           break;
4399         case eKeyNotHandled:
4400           if (ch == 12) { // Ctrl+L, force full redraw
4401             redrawwin(m_window_sp->get());
4402             m_update_screen = true;
4403           }
4404           break;
4405         case eQuitApplication:
4406           done = true;
4407           break;
4408         }
4409       }
4410     }
4411 
4412     debugger.CancelForwardEvents(listener_sp);
4413   }
4414 
GetMainWindow()4415   WindowSP &GetMainWindow() {
4416     if (!m_window_sp)
4417       m_window_sp = std::make_shared<Window>("main", stdscr, false);
4418     return m_window_sp;
4419   }
4420 
TerminalSizeChanged()4421   void TerminalSizeChanged() {
4422     ::endwin();
4423     ::refresh();
4424     Rect content_bounds = m_window_sp->GetFrame();
4425     m_window_sp->SetBounds(content_bounds);
4426     if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar"))
4427       menubar_window_sp->SetBounds(content_bounds.MakeMenuBar());
4428     if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status"))
4429       status_window_sp->SetBounds(content_bounds.MakeStatusBar());
4430 
4431     WindowSP source_window_sp = m_window_sp->FindSubWindow("Source");
4432     WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables");
4433     WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers");
4434     WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads");
4435 
4436     Rect threads_bounds;
4437     Rect source_variables_bounds;
4438     content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
4439                                            threads_bounds);
4440     if (threads_window_sp)
4441       threads_window_sp->SetBounds(threads_bounds);
4442     else
4443       source_variables_bounds = content_bounds;
4444 
4445     Rect source_bounds;
4446     Rect variables_registers_bounds;
4447     source_variables_bounds.HorizontalSplitPercentage(
4448         0.70, source_bounds, variables_registers_bounds);
4449     if (variables_window_sp || registers_window_sp) {
4450       if (variables_window_sp && registers_window_sp) {
4451         Rect variables_bounds;
4452         Rect registers_bounds;
4453         variables_registers_bounds.VerticalSplitPercentage(
4454             0.50, variables_bounds, registers_bounds);
4455         variables_window_sp->SetBounds(variables_bounds);
4456         registers_window_sp->SetBounds(registers_bounds);
4457       } else if (variables_window_sp) {
4458         variables_window_sp->SetBounds(variables_registers_bounds);
4459       } else {
4460         registers_window_sp->SetBounds(variables_registers_bounds);
4461       }
4462     } else {
4463       source_bounds = source_variables_bounds;
4464     }
4465 
4466     source_window_sp->SetBounds(source_bounds);
4467 
4468     touchwin(stdscr);
4469     redrawwin(m_window_sp->get());
4470     m_update_screen = true;
4471   }
4472 
4473 protected:
4474   WindowSP m_window_sp;
4475   WindowDelegates m_window_delegates;
4476   SCREEN *m_screen = nullptr;
4477   FILE *m_in;
4478   FILE *m_out;
4479   bool m_update_screen = false;
4480 };
4481 
4482 } // namespace curses
4483 
4484 using namespace curses;
4485 
4486 struct Row {
4487   ValueObjectUpdater value;
4488   Row *parent;
4489   // The process stop ID when the children were calculated.
4490   uint32_t children_stop_id = 0;
4491   int row_idx = 0;
4492   int x = 1;
4493   int y = 1;
4494   bool might_have_children;
4495   bool expanded = false;
4496   bool calculated_children = false;
4497   std::vector<Row> children;
4498 
RowRow4499   Row(const ValueObjectSP &v, Row *p)
4500       : value(v), parent(p),
4501         might_have_children(v ? v->MightHaveChildren() : false) {}
4502 
GetDepthRow4503   size_t GetDepth() const {
4504     if (parent)
4505       return 1 + parent->GetDepth();
4506     return 0;
4507   }
4508 
ExpandRow4509   void Expand() { expanded = true; }
4510 
GetChildrenRow4511   std::vector<Row> &GetChildren() {
4512     ProcessSP process_sp = value.GetProcessSP();
4513     auto stop_id = process_sp->GetStopID();
4514     if (process_sp && stop_id != children_stop_id) {
4515       children_stop_id = stop_id;
4516       calculated_children = false;
4517     }
4518     if (!calculated_children) {
4519       children.clear();
4520       calculated_children = true;
4521       ValueObjectSP valobj = value.GetSP();
4522       if (valobj) {
4523         const size_t num_children = valobj->GetNumChildren();
4524         for (size_t i = 0; i < num_children; ++i) {
4525           children.push_back(Row(valobj->GetChildAtIndex(i, true), this));
4526         }
4527       }
4528     }
4529     return children;
4530   }
4531 
UnexpandRow4532   void Unexpand() {
4533     expanded = false;
4534     calculated_children = false;
4535     children.clear();
4536   }
4537 
DrawTreeRow4538   void DrawTree(Window &window) {
4539     if (parent)
4540       parent->DrawTreeForChild(window, this, 0);
4541 
4542     if (might_have_children &&
4543         (!calculated_children || !GetChildren().empty())) {
4544       // It we can get UTF8 characters to work we should try to use the
4545       // "symbol" UTF8 string below
4546       //            const char *symbol = "";
4547       //            if (row.expanded)
4548       //                symbol = "\xe2\x96\xbd ";
4549       //            else
4550       //                symbol = "\xe2\x96\xb7 ";
4551       //            window.PutCString (symbol);
4552 
4553       // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v'
4554       // or '>' character...
4555       //            if (expanded)
4556       //                window.PutChar (ACS_DARROW);
4557       //            else
4558       //                window.PutChar (ACS_RARROW);
4559       // Since we can't find any good looking right arrow/down arrow symbols,
4560       // just use a diamond...
4561       window.PutChar(ACS_DIAMOND);
4562       window.PutChar(ACS_HLINE);
4563     }
4564   }
4565 
DrawTreeForChildRow4566   void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) {
4567     if (parent)
4568       parent->DrawTreeForChild(window, this, reverse_depth + 1);
4569 
4570     if (&GetChildren().back() == child) {
4571       // Last child
4572       if (reverse_depth == 0) {
4573         window.PutChar(ACS_LLCORNER);
4574         window.PutChar(ACS_HLINE);
4575       } else {
4576         window.PutChar(' ');
4577         window.PutChar(' ');
4578       }
4579     } else {
4580       if (reverse_depth == 0) {
4581         window.PutChar(ACS_LTEE);
4582         window.PutChar(ACS_HLINE);
4583       } else {
4584         window.PutChar(ACS_VLINE);
4585         window.PutChar(' ');
4586       }
4587     }
4588   }
4589 };
4590 
4591 struct DisplayOptions {
4592   bool show_types;
4593 };
4594 
4595 class TreeItem;
4596 
4597 class TreeDelegate {
4598 public:
4599   TreeDelegate() = default;
4600   virtual ~TreeDelegate() = default;
4601 
4602   virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0;
4603   virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0;
TreeDelegateUpdateSelection(TreeItem & root,int & selection_index,TreeItem * & selected_item)4604   virtual void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
4605                                            TreeItem *&selected_item) {}
4606   // This is invoked when a tree item is selected. If true is returned, the
4607   // views are updated.
4608   virtual bool TreeDelegateItemSelected(TreeItem &item) = 0;
TreeDelegateExpandRootByDefault()4609   virtual bool TreeDelegateExpandRootByDefault() { return false; }
4610   // This is mostly useful for root tree delegates. If false is returned,
4611   // drawing will be skipped completely. This is needed, for instance, in
4612   // skipping drawing of the threads tree if there is no running process.
TreeDelegateShouldDraw()4613   virtual bool TreeDelegateShouldDraw() { return true; }
4614 };
4615 
4616 typedef std::shared_ptr<TreeDelegate> TreeDelegateSP;
4617 
4618 class TreeItem {
4619 public:
TreeItem(TreeItem * parent,TreeDelegate & delegate,bool might_have_children)4620   TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children)
4621       : m_parent(parent), m_delegate(delegate), m_children(),
4622         m_might_have_children(might_have_children) {
4623     if (m_parent == nullptr)
4624       m_is_expanded = m_delegate.TreeDelegateExpandRootByDefault();
4625   }
4626 
operator =(const TreeItem & rhs)4627   TreeItem &operator=(const TreeItem &rhs) {
4628     if (this != &rhs) {
4629       m_parent = rhs.m_parent;
4630       m_delegate = rhs.m_delegate;
4631       m_user_data = rhs.m_user_data;
4632       m_identifier = rhs.m_identifier;
4633       m_row_idx = rhs.m_row_idx;
4634       m_children = rhs.m_children;
4635       m_might_have_children = rhs.m_might_have_children;
4636       m_is_expanded = rhs.m_is_expanded;
4637     }
4638     return *this;
4639   }
4640 
4641   TreeItem(const TreeItem &) = default;
4642 
GetDepth() const4643   size_t GetDepth() const {
4644     if (m_parent)
4645       return 1 + m_parent->GetDepth();
4646     return 0;
4647   }
4648 
GetRowIndex() const4649   int GetRowIndex() const { return m_row_idx; }
4650 
ClearChildren()4651   void ClearChildren() { m_children.clear(); }
4652 
Resize(size_t n,const TreeItem & t)4653   void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); }
4654 
operator [](size_t i)4655   TreeItem &operator[](size_t i) { return m_children[i]; }
4656 
SetRowIndex(int row_idx)4657   void SetRowIndex(int row_idx) { m_row_idx = row_idx; }
4658 
GetNumChildren()4659   size_t GetNumChildren() {
4660     m_delegate.TreeDelegateGenerateChildren(*this);
4661     return m_children.size();
4662   }
4663 
ItemWasSelected()4664   void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); }
4665 
CalculateRowIndexes(int & row_idx)4666   void CalculateRowIndexes(int &row_idx) {
4667     SetRowIndex(row_idx);
4668     ++row_idx;
4669 
4670     const bool expanded = IsExpanded();
4671 
4672     // The root item must calculate its children, or we must calculate the
4673     // number of children if the item is expanded
4674     if (m_parent == nullptr || expanded)
4675       GetNumChildren();
4676 
4677     for (auto &item : m_children) {
4678       if (expanded)
4679         item.CalculateRowIndexes(row_idx);
4680       else
4681         item.SetRowIndex(-1);
4682     }
4683   }
4684 
GetParent()4685   TreeItem *GetParent() { return m_parent; }
4686 
IsExpanded() const4687   bool IsExpanded() const { return m_is_expanded; }
4688 
Expand()4689   void Expand() { m_is_expanded = true; }
4690 
Unexpand()4691   void Unexpand() { m_is_expanded = false; }
4692 
Draw(Window & window,const int first_visible_row,const uint32_t selected_row_idx,int & row_idx,int & num_rows_left)4693   bool Draw(Window &window, const int first_visible_row,
4694             const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) {
4695     if (num_rows_left <= 0)
4696       return false;
4697 
4698     if (m_row_idx >= first_visible_row) {
4699       window.MoveCursor(2, row_idx + 1);
4700 
4701       if (m_parent)
4702         m_parent->DrawTreeForChild(window, this, 0);
4703 
4704       if (m_might_have_children) {
4705         // It we can get UTF8 characters to work we should try to use the
4706         // "symbol" UTF8 string below
4707         //            const char *symbol = "";
4708         //            if (row.expanded)
4709         //                symbol = "\xe2\x96\xbd ";
4710         //            else
4711         //                symbol = "\xe2\x96\xb7 ";
4712         //            window.PutCString (symbol);
4713 
4714         // The ACS_DARROW and ACS_RARROW don't look very nice they are just a
4715         // 'v' or '>' character...
4716         //            if (expanded)
4717         //                window.PutChar (ACS_DARROW);
4718         //            else
4719         //                window.PutChar (ACS_RARROW);
4720         // Since we can't find any good looking right arrow/down arrow symbols,
4721         // just use a diamond...
4722         window.PutChar(ACS_DIAMOND);
4723         window.PutChar(ACS_HLINE);
4724       }
4725       bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) &&
4726                        window.IsActive();
4727 
4728       if (highlight)
4729         window.AttributeOn(A_REVERSE);
4730 
4731       m_delegate.TreeDelegateDrawTreeItem(*this, window);
4732 
4733       if (highlight)
4734         window.AttributeOff(A_REVERSE);
4735       ++row_idx;
4736       --num_rows_left;
4737     }
4738 
4739     if (num_rows_left <= 0)
4740       return false; // We are done drawing...
4741 
4742     if (IsExpanded()) {
4743       for (auto &item : m_children) {
4744         // If we displayed all the rows and item.Draw() returns false we are
4745         // done drawing and can exit this for loop
4746         if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx,
4747                        num_rows_left))
4748           break;
4749       }
4750     }
4751     return num_rows_left >= 0; // Return true if not done drawing yet
4752   }
4753 
DrawTreeForChild(Window & window,TreeItem * child,uint32_t reverse_depth)4754   void DrawTreeForChild(Window &window, TreeItem *child,
4755                         uint32_t reverse_depth) {
4756     if (m_parent)
4757       m_parent->DrawTreeForChild(window, this, reverse_depth + 1);
4758 
4759     if (&m_children.back() == child) {
4760       // Last child
4761       if (reverse_depth == 0) {
4762         window.PutChar(ACS_LLCORNER);
4763         window.PutChar(ACS_HLINE);
4764       } else {
4765         window.PutChar(' ');
4766         window.PutChar(' ');
4767       }
4768     } else {
4769       if (reverse_depth == 0) {
4770         window.PutChar(ACS_LTEE);
4771         window.PutChar(ACS_HLINE);
4772       } else {
4773         window.PutChar(ACS_VLINE);
4774         window.PutChar(' ');
4775       }
4776     }
4777   }
4778 
GetItemForRowIndex(uint32_t row_idx)4779   TreeItem *GetItemForRowIndex(uint32_t row_idx) {
4780     if (static_cast<uint32_t>(m_row_idx) == row_idx)
4781       return this;
4782     if (m_children.empty())
4783       return nullptr;
4784     if (IsExpanded()) {
4785       for (auto &item : m_children) {
4786         TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx);
4787         if (selected_item_ptr)
4788           return selected_item_ptr;
4789       }
4790     }
4791     return nullptr;
4792   }
4793 
GetUserData() const4794   void *GetUserData() const { return m_user_data; }
4795 
SetUserData(void * user_data)4796   void SetUserData(void *user_data) { m_user_data = user_data; }
4797 
GetIdentifier() const4798   uint64_t GetIdentifier() const { return m_identifier; }
4799 
SetIdentifier(uint64_t identifier)4800   void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
4801 
GetText() const4802   const std::string &GetText() const { return m_text; }
4803 
SetText(const char * text)4804   void SetText(const char *text) {
4805     if (text == nullptr) {
4806       m_text.clear();
4807       return;
4808     }
4809     m_text = text;
4810   }
4811 
SetMightHaveChildren(bool b)4812   void SetMightHaveChildren(bool b) { m_might_have_children = b; }
4813 
4814 protected:
4815   TreeItem *m_parent;
4816   TreeDelegate &m_delegate;
4817   void *m_user_data = nullptr;
4818   uint64_t m_identifier = 0;
4819   std::string m_text;
4820   int m_row_idx = -1; // Zero based visible row index, -1 if not visible or for
4821                       // the root item
4822   std::vector<TreeItem> m_children;
4823   bool m_might_have_children;
4824   bool m_is_expanded = false;
4825 };
4826 
4827 class TreeWindowDelegate : public WindowDelegate {
4828 public:
TreeWindowDelegate(Debugger & debugger,const TreeDelegateSP & delegate_sp)4829   TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp)
4830       : m_debugger(debugger), m_delegate_sp(delegate_sp),
4831         m_root(nullptr, *delegate_sp, true) {}
4832 
NumVisibleRows() const4833   int NumVisibleRows() const { return m_max_y - m_min_y; }
4834 
WindowDelegateDraw(Window & window,bool force)4835   bool WindowDelegateDraw(Window &window, bool force) override {
4836     m_min_x = 2;
4837     m_min_y = 1;
4838     m_max_x = window.GetWidth() - 1;
4839     m_max_y = window.GetHeight() - 1;
4840 
4841     window.Erase();
4842     window.DrawTitleBox(window.GetName());
4843 
4844     if (!m_delegate_sp->TreeDelegateShouldDraw()) {
4845       m_selected_item = nullptr;
4846       return true;
4847     }
4848 
4849     const int num_visible_rows = NumVisibleRows();
4850     m_num_rows = 0;
4851     m_root.CalculateRowIndexes(m_num_rows);
4852     m_delegate_sp->TreeDelegateUpdateSelection(m_root, m_selected_row_idx,
4853                                                m_selected_item);
4854 
4855     // If we unexpanded while having something selected our total number of
4856     // rows is less than the num visible rows, then make sure we show all the
4857     // rows by setting the first visible row accordingly.
4858     if (m_first_visible_row > 0 && m_num_rows < num_visible_rows)
4859       m_first_visible_row = 0;
4860 
4861     // Make sure the selected row is always visible
4862     if (m_selected_row_idx < m_first_visible_row)
4863       m_first_visible_row = m_selected_row_idx;
4864     else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
4865       m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
4866 
4867     int row_idx = 0;
4868     int num_rows_left = num_visible_rows;
4869     m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx,
4870                 num_rows_left);
4871     // Get the selected row
4872     m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4873 
4874     return true; // Drawing handled
4875   }
4876 
WindowDelegateGetHelpText()4877   const char *WindowDelegateGetHelpText() override {
4878     return "Thread window keyboard shortcuts:";
4879   }
4880 
WindowDelegateGetKeyHelp()4881   KeyHelp *WindowDelegateGetKeyHelp() override {
4882     static curses::KeyHelp g_source_view_key_help[] = {
4883         {KEY_UP, "Select previous item"},
4884         {KEY_DOWN, "Select next item"},
4885         {KEY_RIGHT, "Expand the selected item"},
4886         {KEY_LEFT,
4887          "Unexpand the selected item or select parent if not expanded"},
4888         {KEY_PPAGE, "Page up"},
4889         {KEY_NPAGE, "Page down"},
4890         {'h', "Show help dialog"},
4891         {' ', "Toggle item expansion"},
4892         {',', "Page up"},
4893         {'.', "Page down"},
4894         {'\0', nullptr}};
4895     return g_source_view_key_help;
4896   }
4897 
WindowDelegateHandleChar(Window & window,int c)4898   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
4899     switch (c) {
4900     case ',':
4901     case KEY_PPAGE:
4902       // Page up key
4903       if (m_first_visible_row > 0) {
4904         if (m_first_visible_row > m_max_y)
4905           m_first_visible_row -= m_max_y;
4906         else
4907           m_first_visible_row = 0;
4908         m_selected_row_idx = m_first_visible_row;
4909         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4910         if (m_selected_item)
4911           m_selected_item->ItemWasSelected();
4912       }
4913       return eKeyHandled;
4914 
4915     case '.':
4916     case KEY_NPAGE:
4917       // Page down key
4918       if (m_num_rows > m_max_y) {
4919         if (m_first_visible_row + m_max_y < m_num_rows) {
4920           m_first_visible_row += m_max_y;
4921           m_selected_row_idx = m_first_visible_row;
4922           m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4923           if (m_selected_item)
4924             m_selected_item->ItemWasSelected();
4925         }
4926       }
4927       return eKeyHandled;
4928 
4929     case KEY_UP:
4930       if (m_selected_row_idx > 0) {
4931         --m_selected_row_idx;
4932         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4933         if (m_selected_item)
4934           m_selected_item->ItemWasSelected();
4935       }
4936       return eKeyHandled;
4937 
4938     case KEY_DOWN:
4939       if (m_selected_row_idx + 1 < m_num_rows) {
4940         ++m_selected_row_idx;
4941         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4942         if (m_selected_item)
4943           m_selected_item->ItemWasSelected();
4944       }
4945       return eKeyHandled;
4946 
4947     case KEY_RIGHT:
4948       if (m_selected_item) {
4949         if (!m_selected_item->IsExpanded())
4950           m_selected_item->Expand();
4951       }
4952       return eKeyHandled;
4953 
4954     case KEY_LEFT:
4955       if (m_selected_item) {
4956         if (m_selected_item->IsExpanded())
4957           m_selected_item->Unexpand();
4958         else if (m_selected_item->GetParent()) {
4959           m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex();
4960           m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4961           if (m_selected_item)
4962             m_selected_item->ItemWasSelected();
4963         }
4964       }
4965       return eKeyHandled;
4966 
4967     case ' ':
4968       // Toggle expansion state when SPACE is pressed
4969       if (m_selected_item) {
4970         if (m_selected_item->IsExpanded())
4971           m_selected_item->Unexpand();
4972         else
4973           m_selected_item->Expand();
4974       }
4975       return eKeyHandled;
4976 
4977     case 'h':
4978       window.CreateHelpSubwindow();
4979       return eKeyHandled;
4980 
4981     default:
4982       break;
4983     }
4984     return eKeyNotHandled;
4985   }
4986 
4987 protected:
4988   Debugger &m_debugger;
4989   TreeDelegateSP m_delegate_sp;
4990   TreeItem m_root;
4991   TreeItem *m_selected_item = nullptr;
4992   int m_num_rows = 0;
4993   int m_selected_row_idx = 0;
4994   int m_first_visible_row = 0;
4995   int m_min_x = 0;
4996   int m_min_y = 0;
4997   int m_max_x = 0;
4998   int m_max_y = 0;
4999 };
5000 
5001 // A tree delegate that just draws the text member of the tree item, it doesn't
5002 // have any children or actions.
5003 class TextTreeDelegate : public TreeDelegate {
5004 public:
TextTreeDelegate()5005   TextTreeDelegate() : TreeDelegate() {}
5006 
5007   ~TextTreeDelegate() override = default;
5008 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5009   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5010     window.PutCStringTruncated(1, item.GetText().c_str());
5011   }
5012 
TreeDelegateGenerateChildren(TreeItem & item)5013   void TreeDelegateGenerateChildren(TreeItem &item) override {}
5014 
TreeDelegateItemSelected(TreeItem & item)5015   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5016 };
5017 
5018 class FrameTreeDelegate : public TreeDelegate {
5019 public:
FrameTreeDelegate()5020   FrameTreeDelegate() : TreeDelegate() {
5021     FormatEntity::Parse(
5022         "#${frame.index}: {${function.name}${function.pc-offset}}}", m_format);
5023   }
5024 
5025   ~FrameTreeDelegate() override = default;
5026 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5027   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5028     Thread *thread = (Thread *)item.GetUserData();
5029     if (thread) {
5030       const uint64_t frame_idx = item.GetIdentifier();
5031       StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx);
5032       if (frame_sp) {
5033         StreamString strm;
5034         const SymbolContext &sc =
5035             frame_sp->GetSymbolContext(eSymbolContextEverything);
5036         ExecutionContext exe_ctx(frame_sp);
5037         if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr,
5038                                  nullptr, false, false)) {
5039           int right_pad = 1;
5040           window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
5041         }
5042       }
5043     }
5044   }
5045 
TreeDelegateGenerateChildren(TreeItem & item)5046   void TreeDelegateGenerateChildren(TreeItem &item) override {
5047     // No children for frames yet...
5048   }
5049 
TreeDelegateItemSelected(TreeItem & item)5050   bool TreeDelegateItemSelected(TreeItem &item) override {
5051     Thread *thread = (Thread *)item.GetUserData();
5052     if (thread) {
5053       thread->GetProcess()->GetThreadList().SetSelectedThreadByID(
5054           thread->GetID());
5055       const uint64_t frame_idx = item.GetIdentifier();
5056       thread->SetSelectedFrameByIndex(frame_idx);
5057       return true;
5058     }
5059     return false;
5060   }
5061 
5062 protected:
5063   FormatEntity::Entry m_format;
5064 };
5065 
5066 class ThreadTreeDelegate : public TreeDelegate {
5067 public:
ThreadTreeDelegate(Debugger & debugger)5068   ThreadTreeDelegate(Debugger &debugger)
5069       : TreeDelegate(), m_debugger(debugger) {
5070     FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop "
5071                         "reason = ${thread.stop-reason}}",
5072                         m_format);
5073   }
5074 
5075   ~ThreadTreeDelegate() override = default;
5076 
GetProcess()5077   ProcessSP GetProcess() {
5078     return m_debugger.GetCommandInterpreter()
5079         .GetExecutionContext()
5080         .GetProcessSP();
5081   }
5082 
GetThread(const TreeItem & item)5083   ThreadSP GetThread(const TreeItem &item) {
5084     ProcessSP process_sp = GetProcess();
5085     if (process_sp)
5086       return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier());
5087     return ThreadSP();
5088   }
5089 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5090   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5091     ThreadSP thread_sp = GetThread(item);
5092     if (thread_sp) {
5093       StreamString strm;
5094       ExecutionContext exe_ctx(thread_sp);
5095       if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
5096                                nullptr, false, false)) {
5097         int right_pad = 1;
5098         window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
5099       }
5100     }
5101   }
5102 
TreeDelegateGenerateChildren(TreeItem & item)5103   void TreeDelegateGenerateChildren(TreeItem &item) override {
5104     ProcessSP process_sp = GetProcess();
5105     if (process_sp && process_sp->IsAlive()) {
5106       StateType state = process_sp->GetState();
5107       if (StateIsStoppedState(state, true)) {
5108         ThreadSP thread_sp = GetThread(item);
5109         if (thread_sp) {
5110           if (m_stop_id == process_sp->GetStopID() &&
5111               thread_sp->GetID() == m_tid)
5112             return; // Children are already up to date
5113           if (!m_frame_delegate_sp) {
5114             // Always expand the thread item the first time we show it
5115             m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>();
5116           }
5117 
5118           m_stop_id = process_sp->GetStopID();
5119           m_tid = thread_sp->GetID();
5120 
5121           TreeItem t(&item, *m_frame_delegate_sp, false);
5122           size_t num_frames = thread_sp->GetStackFrameCount();
5123           item.Resize(num_frames, t);
5124           for (size_t i = 0; i < num_frames; ++i) {
5125             item[i].SetUserData(thread_sp.get());
5126             item[i].SetIdentifier(i);
5127           }
5128         }
5129         return;
5130       }
5131     }
5132     item.ClearChildren();
5133   }
5134 
TreeDelegateItemSelected(TreeItem & item)5135   bool TreeDelegateItemSelected(TreeItem &item) override {
5136     ProcessSP process_sp = GetProcess();
5137     if (process_sp && process_sp->IsAlive()) {
5138       StateType state = process_sp->GetState();
5139       if (StateIsStoppedState(state, true)) {
5140         ThreadSP thread_sp = GetThread(item);
5141         if (thread_sp) {
5142           ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList();
5143           std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex());
5144           ThreadSP selected_thread_sp = thread_list.GetSelectedThread();
5145           if (selected_thread_sp->GetID() != thread_sp->GetID()) {
5146             thread_list.SetSelectedThreadByID(thread_sp->GetID());
5147             return true;
5148           }
5149         }
5150       }
5151     }
5152     return false;
5153   }
5154 
5155 protected:
5156   Debugger &m_debugger;
5157   std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp;
5158   lldb::user_id_t m_tid = LLDB_INVALID_THREAD_ID;
5159   uint32_t m_stop_id = UINT32_MAX;
5160   FormatEntity::Entry m_format;
5161 };
5162 
5163 class ThreadsTreeDelegate : public TreeDelegate {
5164 public:
ThreadsTreeDelegate(Debugger & debugger)5165   ThreadsTreeDelegate(Debugger &debugger)
5166       : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger) {
5167     FormatEntity::Parse("process ${process.id}{, name = ${process.name}}",
5168                         m_format);
5169   }
5170 
5171   ~ThreadsTreeDelegate() override = default;
5172 
GetProcess()5173   ProcessSP GetProcess() {
5174     return m_debugger.GetCommandInterpreter()
5175         .GetExecutionContext()
5176         .GetProcessSP();
5177   }
5178 
TreeDelegateShouldDraw()5179   bool TreeDelegateShouldDraw() override {
5180     ProcessSP process = GetProcess();
5181     if (!process)
5182       return false;
5183 
5184     if (StateIsRunningState(process->GetState()))
5185       return false;
5186 
5187     return true;
5188   }
5189 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5190   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5191     ProcessSP process_sp = GetProcess();
5192     if (process_sp && process_sp->IsAlive()) {
5193       StreamString strm;
5194       ExecutionContext exe_ctx(process_sp);
5195       if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
5196                                nullptr, false, false)) {
5197         int right_pad = 1;
5198         window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
5199       }
5200     }
5201   }
5202 
TreeDelegateGenerateChildren(TreeItem & item)5203   void TreeDelegateGenerateChildren(TreeItem &item) override {
5204     ProcessSP process_sp = GetProcess();
5205     m_update_selection = false;
5206     if (process_sp && process_sp->IsAlive()) {
5207       StateType state = process_sp->GetState();
5208       if (StateIsStoppedState(state, true)) {
5209         const uint32_t stop_id = process_sp->GetStopID();
5210         if (m_stop_id == stop_id)
5211           return; // Children are already up to date
5212 
5213         m_stop_id = stop_id;
5214         m_update_selection = true;
5215 
5216         if (!m_thread_delegate_sp) {
5217           // Always expand the thread item the first time we show it
5218           // item.Expand();
5219           m_thread_delegate_sp =
5220               std::make_shared<ThreadTreeDelegate>(m_debugger);
5221         }
5222 
5223         TreeItem t(&item, *m_thread_delegate_sp, false);
5224         ThreadList &threads = process_sp->GetThreadList();
5225         std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
5226         ThreadSP selected_thread = threads.GetSelectedThread();
5227         size_t num_threads = threads.GetSize();
5228         item.Resize(num_threads, t);
5229         for (size_t i = 0; i < num_threads; ++i) {
5230           ThreadSP thread = threads.GetThreadAtIndex(i);
5231           item[i].SetIdentifier(thread->GetID());
5232           item[i].SetMightHaveChildren(true);
5233           if (selected_thread->GetID() == thread->GetID())
5234             item[i].Expand();
5235         }
5236         return;
5237       }
5238     }
5239     item.ClearChildren();
5240   }
5241 
TreeDelegateUpdateSelection(TreeItem & root,int & selection_index,TreeItem * & selected_item)5242   void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
5243                                    TreeItem *&selected_item) override {
5244     if (!m_update_selection)
5245       return;
5246 
5247     ProcessSP process_sp = GetProcess();
5248     if (!(process_sp && process_sp->IsAlive()))
5249       return;
5250 
5251     StateType state = process_sp->GetState();
5252     if (!StateIsStoppedState(state, true))
5253       return;
5254 
5255     ThreadList &threads = process_sp->GetThreadList();
5256     std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
5257     ThreadSP selected_thread = threads.GetSelectedThread();
5258     size_t num_threads = threads.GetSize();
5259     for (size_t i = 0; i < num_threads; ++i) {
5260       ThreadSP thread = threads.GetThreadAtIndex(i);
5261       if (selected_thread->GetID() == thread->GetID()) {
5262         selected_item = &root[i][thread->GetSelectedFrameIndex()];
5263         selection_index = selected_item->GetRowIndex();
5264         return;
5265       }
5266     }
5267   }
5268 
TreeDelegateItemSelected(TreeItem & item)5269   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5270 
TreeDelegateExpandRootByDefault()5271   bool TreeDelegateExpandRootByDefault() override { return true; }
5272 
5273 protected:
5274   std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp;
5275   Debugger &m_debugger;
5276   uint32_t m_stop_id = UINT32_MAX;
5277   bool m_update_selection = false;
5278   FormatEntity::Entry m_format;
5279 };
5280 
5281 class BreakpointLocationTreeDelegate : public TreeDelegate {
5282 public:
BreakpointLocationTreeDelegate(Debugger & debugger)5283   BreakpointLocationTreeDelegate(Debugger &debugger)
5284       : TreeDelegate(), m_debugger(debugger) {}
5285 
5286   ~BreakpointLocationTreeDelegate() override = default;
5287 
GetProcess()5288   Process *GetProcess() {
5289     ExecutionContext exe_ctx(
5290         m_debugger.GetCommandInterpreter().GetExecutionContext());
5291     return exe_ctx.GetProcessPtr();
5292   }
5293 
GetBreakpointLocation(const TreeItem & item)5294   BreakpointLocationSP GetBreakpointLocation(const TreeItem &item) {
5295     Breakpoint *breakpoint = (Breakpoint *)item.GetUserData();
5296     return breakpoint->GetLocationAtIndex(item.GetIdentifier());
5297   }
5298 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5299   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5300     BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item);
5301     Process *process = GetProcess();
5302     StreamString stream;
5303     stream.Printf("%i.%i: ", breakpoint_location->GetBreakpoint().GetID(),
5304                   breakpoint_location->GetID());
5305     Address address = breakpoint_location->GetAddress();
5306     address.Dump(&stream, process, Address::DumpStyleResolvedDescription,
5307                  Address::DumpStyleInvalid);
5308     window.PutCStringTruncated(1, stream.GetString().str().c_str());
5309   }
5310 
ComputeDetailsList(BreakpointLocationSP breakpoint_location)5311   StringList ComputeDetailsList(BreakpointLocationSP breakpoint_location) {
5312     StringList details;
5313 
5314     Address address = breakpoint_location->GetAddress();
5315     SymbolContext symbol_context;
5316     address.CalculateSymbolContext(&symbol_context);
5317 
5318     if (symbol_context.module_sp) {
5319       StreamString module_stream;
5320       module_stream.PutCString("module = ");
5321       symbol_context.module_sp->GetFileSpec().Dump(
5322           module_stream.AsRawOstream());
5323       details.AppendString(module_stream.GetString());
5324     }
5325 
5326     if (symbol_context.comp_unit != nullptr) {
5327       StreamString compile_unit_stream;
5328       compile_unit_stream.PutCString("compile unit = ");
5329       symbol_context.comp_unit->GetPrimaryFile().GetFilename().Dump(
5330           &compile_unit_stream);
5331       details.AppendString(compile_unit_stream.GetString());
5332 
5333       if (symbol_context.function != nullptr) {
5334         StreamString function_stream;
5335         function_stream.PutCString("function = ");
5336         function_stream.PutCString(
5337             symbol_context.function->GetName().AsCString("<unknown>"));
5338         details.AppendString(function_stream.GetString());
5339       }
5340 
5341       if (symbol_context.line_entry.line > 0) {
5342         StreamString location_stream;
5343         location_stream.PutCString("location = ");
5344         symbol_context.line_entry.DumpStopContext(&location_stream, true);
5345         details.AppendString(location_stream.GetString());
5346       }
5347 
5348     } else {
5349       if (symbol_context.symbol) {
5350         StreamString symbol_stream;
5351         if (breakpoint_location->IsReExported())
5352           symbol_stream.PutCString("re-exported target = ");
5353         else
5354           symbol_stream.PutCString("symbol = ");
5355         symbol_stream.PutCString(
5356             symbol_context.symbol->GetName().AsCString("<unknown>"));
5357         details.AppendString(symbol_stream.GetString());
5358       }
5359     }
5360 
5361     Process *process = GetProcess();
5362 
5363     StreamString address_stream;
5364     address.Dump(&address_stream, process, Address::DumpStyleLoadAddress,
5365                  Address::DumpStyleModuleWithFileAddress);
5366     details.AppendString(address_stream.GetString());
5367 
5368     BreakpointSiteSP breakpoint_site = breakpoint_location->GetBreakpointSite();
5369     if (breakpoint_location->IsIndirect() && breakpoint_site) {
5370       Address resolved_address;
5371       resolved_address.SetLoadAddress(breakpoint_site->GetLoadAddress(),
5372                                       &breakpoint_location->GetTarget());
5373       Symbol *resolved_symbol = resolved_address.CalculateSymbolContextSymbol();
5374       if (resolved_symbol) {
5375         StreamString indirect_target_stream;
5376         indirect_target_stream.PutCString("indirect target = ");
5377         indirect_target_stream.PutCString(
5378             resolved_symbol->GetName().GetCString());
5379         details.AppendString(indirect_target_stream.GetString());
5380       }
5381     }
5382 
5383     bool is_resolved = breakpoint_location->IsResolved();
5384     StreamString resolved_stream;
5385     resolved_stream.Printf("resolved = %s", is_resolved ? "true" : "false");
5386     details.AppendString(resolved_stream.GetString());
5387 
5388     bool is_hardware = is_resolved && breakpoint_site->IsHardware();
5389     StreamString hardware_stream;
5390     hardware_stream.Printf("hardware = %s", is_hardware ? "true" : "false");
5391     details.AppendString(hardware_stream.GetString());
5392 
5393     StreamString hit_count_stream;
5394     hit_count_stream.Printf("hit count = %-4u",
5395                             breakpoint_location->GetHitCount());
5396     details.AppendString(hit_count_stream.GetString());
5397 
5398     return details;
5399   }
5400 
TreeDelegateGenerateChildren(TreeItem & item)5401   void TreeDelegateGenerateChildren(TreeItem &item) override {
5402     BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item);
5403     StringList details = ComputeDetailsList(breakpoint_location);
5404 
5405     if (!m_string_delegate_sp)
5406       m_string_delegate_sp = std::make_shared<TextTreeDelegate>();
5407     TreeItem details_tree_item(&item, *m_string_delegate_sp, false);
5408 
5409     item.Resize(details.GetSize(), details_tree_item);
5410     for (size_t i = 0; i < details.GetSize(); i++) {
5411       item[i].SetText(details.GetStringAtIndex(i));
5412     }
5413   }
5414 
TreeDelegateItemSelected(TreeItem & item)5415   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5416 
5417 protected:
5418   Debugger &m_debugger;
5419   std::shared_ptr<TextTreeDelegate> m_string_delegate_sp;
5420 };
5421 
5422 class BreakpointTreeDelegate : public TreeDelegate {
5423 public:
BreakpointTreeDelegate(Debugger & debugger)5424   BreakpointTreeDelegate(Debugger &debugger)
5425       : TreeDelegate(), m_debugger(debugger),
5426         m_breakpoint_location_delegate_sp() {}
5427 
5428   ~BreakpointTreeDelegate() override = default;
5429 
GetBreakpoint(const TreeItem & item)5430   BreakpointSP GetBreakpoint(const TreeItem &item) {
5431     TargetSP target = m_debugger.GetSelectedTarget();
5432     BreakpointList &breakpoints = target->GetBreakpointList(false);
5433     return breakpoints.GetBreakpointAtIndex(item.GetIdentifier());
5434   }
5435 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5436   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5437     BreakpointSP breakpoint = GetBreakpoint(item);
5438     StreamString stream;
5439     stream.Format("{0}: ", breakpoint->GetID());
5440     breakpoint->GetResolverDescription(&stream);
5441     breakpoint->GetFilterDescription(&stream);
5442     window.PutCStringTruncated(1, stream.GetString().str().c_str());
5443   }
5444 
TreeDelegateGenerateChildren(TreeItem & item)5445   void TreeDelegateGenerateChildren(TreeItem &item) override {
5446     BreakpointSP breakpoint = GetBreakpoint(item);
5447 
5448     if (!m_breakpoint_location_delegate_sp)
5449       m_breakpoint_location_delegate_sp =
5450           std::make_shared<BreakpointLocationTreeDelegate>(m_debugger);
5451     TreeItem breakpoint_location_tree_item(
5452         &item, *m_breakpoint_location_delegate_sp, true);
5453 
5454     item.Resize(breakpoint->GetNumLocations(), breakpoint_location_tree_item);
5455     for (size_t i = 0; i < breakpoint->GetNumLocations(); i++) {
5456       item[i].SetIdentifier(i);
5457       item[i].SetUserData(breakpoint.get());
5458     }
5459   }
5460 
TreeDelegateItemSelected(TreeItem & item)5461   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5462 
5463 protected:
5464   Debugger &m_debugger;
5465   std::shared_ptr<BreakpointLocationTreeDelegate>
5466       m_breakpoint_location_delegate_sp;
5467 };
5468 
5469 class BreakpointsTreeDelegate : public TreeDelegate {
5470 public:
BreakpointsTreeDelegate(Debugger & debugger)5471   BreakpointsTreeDelegate(Debugger &debugger)
5472       : TreeDelegate(), m_debugger(debugger), m_breakpoint_delegate_sp() {}
5473 
5474   ~BreakpointsTreeDelegate() override = default;
5475 
TreeDelegateShouldDraw()5476   bool TreeDelegateShouldDraw() override {
5477     TargetSP target = m_debugger.GetSelectedTarget();
5478     if (!target)
5479       return false;
5480 
5481     return true;
5482   }
5483 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5484   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5485     window.PutCString("Breakpoints");
5486   }
5487 
TreeDelegateGenerateChildren(TreeItem & item)5488   void TreeDelegateGenerateChildren(TreeItem &item) override {
5489     TargetSP target = m_debugger.GetSelectedTarget();
5490 
5491     BreakpointList &breakpoints = target->GetBreakpointList(false);
5492     std::unique_lock<std::recursive_mutex> lock;
5493     breakpoints.GetListMutex(lock);
5494 
5495     if (!m_breakpoint_delegate_sp)
5496       m_breakpoint_delegate_sp =
5497           std::make_shared<BreakpointTreeDelegate>(m_debugger);
5498     TreeItem breakpoint_tree_item(&item, *m_breakpoint_delegate_sp, true);
5499 
5500     item.Resize(breakpoints.GetSize(), breakpoint_tree_item);
5501     for (size_t i = 0; i < breakpoints.GetSize(); i++) {
5502       item[i].SetIdentifier(i);
5503     }
5504   }
5505 
TreeDelegateItemSelected(TreeItem & item)5506   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5507 
TreeDelegateExpandRootByDefault()5508   bool TreeDelegateExpandRootByDefault() override { return true; }
5509 
5510 protected:
5511   Debugger &m_debugger;
5512   std::shared_ptr<BreakpointTreeDelegate> m_breakpoint_delegate_sp;
5513 };
5514 
5515 class ValueObjectListDelegate : public WindowDelegate {
5516 public:
ValueObjectListDelegate()5517   ValueObjectListDelegate() : m_rows() {}
5518 
ValueObjectListDelegate(ValueObjectList & valobj_list)5519   ValueObjectListDelegate(ValueObjectList &valobj_list) : m_rows() {
5520     SetValues(valobj_list);
5521   }
5522 
5523   ~ValueObjectListDelegate() override = default;
5524 
SetValues(ValueObjectList & valobj_list)5525   void SetValues(ValueObjectList &valobj_list) {
5526     m_selected_row = nullptr;
5527     m_selected_row_idx = 0;
5528     m_first_visible_row = 0;
5529     m_num_rows = 0;
5530     m_rows.clear();
5531     for (auto &valobj_sp : valobj_list.GetObjects())
5532       m_rows.push_back(Row(valobj_sp, nullptr));
5533   }
5534 
WindowDelegateDraw(Window & window,bool force)5535   bool WindowDelegateDraw(Window &window, bool force) override {
5536     m_num_rows = 0;
5537     m_min_x = 2;
5538     m_min_y = 1;
5539     m_max_x = window.GetWidth() - 1;
5540     m_max_y = window.GetHeight() - 1;
5541 
5542     window.Erase();
5543     window.DrawTitleBox(window.GetName());
5544 
5545     const int num_visible_rows = NumVisibleRows();
5546     const int num_rows = CalculateTotalNumberRows(m_rows);
5547 
5548     // If we unexpanded while having something selected our total number of
5549     // rows is less than the num visible rows, then make sure we show all the
5550     // rows by setting the first visible row accordingly.
5551     if (m_first_visible_row > 0 && num_rows < num_visible_rows)
5552       m_first_visible_row = 0;
5553 
5554     // Make sure the selected row is always visible
5555     if (m_selected_row_idx < m_first_visible_row)
5556       m_first_visible_row = m_selected_row_idx;
5557     else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
5558       m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
5559 
5560     DisplayRows(window, m_rows, g_options);
5561 
5562     // Get the selected row
5563     m_selected_row = GetRowForRowIndex(m_selected_row_idx);
5564     // Keep the cursor on the selected row so the highlight and the cursor are
5565     // always on the same line
5566     if (m_selected_row)
5567       window.MoveCursor(m_selected_row->x, m_selected_row->y);
5568 
5569     return true; // Drawing handled
5570   }
5571 
WindowDelegateGetKeyHelp()5572   KeyHelp *WindowDelegateGetKeyHelp() override {
5573     static curses::KeyHelp g_source_view_key_help[] = {
5574         {KEY_UP, "Select previous item"},
5575         {KEY_DOWN, "Select next item"},
5576         {KEY_RIGHT, "Expand selected item"},
5577         {KEY_LEFT, "Unexpand selected item or select parent if not expanded"},
5578         {KEY_PPAGE, "Page up"},
5579         {KEY_NPAGE, "Page down"},
5580         {'A', "Format as annotated address"},
5581         {'b', "Format as binary"},
5582         {'B', "Format as hex bytes with ASCII"},
5583         {'c', "Format as character"},
5584         {'d', "Format as a signed integer"},
5585         {'D', "Format selected value using the default format for the type"},
5586         {'f', "Format as float"},
5587         {'h', "Show help dialog"},
5588         {'i', "Format as instructions"},
5589         {'o', "Format as octal"},
5590         {'p', "Format as pointer"},
5591         {'s', "Format as C string"},
5592         {'t', "Toggle showing/hiding type names"},
5593         {'u', "Format as an unsigned integer"},
5594         {'x', "Format as hex"},
5595         {'X', "Format as uppercase hex"},
5596         {' ', "Toggle item expansion"},
5597         {',', "Page up"},
5598         {'.', "Page down"},
5599         {'\0', nullptr}};
5600     return g_source_view_key_help;
5601   }
5602 
WindowDelegateHandleChar(Window & window,int c)5603   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
5604     switch (c) {
5605     case 'x':
5606     case 'X':
5607     case 'o':
5608     case 's':
5609     case 'u':
5610     case 'd':
5611     case 'D':
5612     case 'i':
5613     case 'A':
5614     case 'p':
5615     case 'c':
5616     case 'b':
5617     case 'B':
5618     case 'f':
5619       // Change the format for the currently selected item
5620       if (m_selected_row) {
5621         auto valobj_sp = m_selected_row->value.GetSP();
5622         if (valobj_sp)
5623           valobj_sp->SetFormat(FormatForChar(c));
5624       }
5625       return eKeyHandled;
5626 
5627     case 't':
5628       // Toggle showing type names
5629       g_options.show_types = !g_options.show_types;
5630       return eKeyHandled;
5631 
5632     case ',':
5633     case KEY_PPAGE:
5634       // Page up key
5635       if (m_first_visible_row > 0) {
5636         if (static_cast<int>(m_first_visible_row) > m_max_y)
5637           m_first_visible_row -= m_max_y;
5638         else
5639           m_first_visible_row = 0;
5640         m_selected_row_idx = m_first_visible_row;
5641       }
5642       return eKeyHandled;
5643 
5644     case '.':
5645     case KEY_NPAGE:
5646       // Page down key
5647       if (m_num_rows > static_cast<size_t>(m_max_y)) {
5648         if (m_first_visible_row + m_max_y < m_num_rows) {
5649           m_first_visible_row += m_max_y;
5650           m_selected_row_idx = m_first_visible_row;
5651         }
5652       }
5653       return eKeyHandled;
5654 
5655     case KEY_UP:
5656       if (m_selected_row_idx > 0)
5657         --m_selected_row_idx;
5658       return eKeyHandled;
5659 
5660     case KEY_DOWN:
5661       if (m_selected_row_idx + 1 < m_num_rows)
5662         ++m_selected_row_idx;
5663       return eKeyHandled;
5664 
5665     case KEY_RIGHT:
5666       if (m_selected_row) {
5667         if (!m_selected_row->expanded)
5668           m_selected_row->Expand();
5669       }
5670       return eKeyHandled;
5671 
5672     case KEY_LEFT:
5673       if (m_selected_row) {
5674         if (m_selected_row->expanded)
5675           m_selected_row->Unexpand();
5676         else if (m_selected_row->parent)
5677           m_selected_row_idx = m_selected_row->parent->row_idx;
5678       }
5679       return eKeyHandled;
5680 
5681     case ' ':
5682       // Toggle expansion state when SPACE is pressed
5683       if (m_selected_row) {
5684         if (m_selected_row->expanded)
5685           m_selected_row->Unexpand();
5686         else
5687           m_selected_row->Expand();
5688       }
5689       return eKeyHandled;
5690 
5691     case 'h':
5692       window.CreateHelpSubwindow();
5693       return eKeyHandled;
5694 
5695     default:
5696       break;
5697     }
5698     return eKeyNotHandled;
5699   }
5700 
5701 protected:
5702   std::vector<Row> m_rows;
5703   Row *m_selected_row = nullptr;
5704   uint32_t m_selected_row_idx = 0;
5705   uint32_t m_first_visible_row = 0;
5706   uint32_t m_num_rows = 0;
5707   int m_min_x = 0;
5708   int m_min_y = 0;
5709   int m_max_x = 0;
5710   int m_max_y = 0;
5711 
FormatForChar(int c)5712   static Format FormatForChar(int c) {
5713     switch (c) {
5714     case 'x':
5715       return eFormatHex;
5716     case 'X':
5717       return eFormatHexUppercase;
5718     case 'o':
5719       return eFormatOctal;
5720     case 's':
5721       return eFormatCString;
5722     case 'u':
5723       return eFormatUnsigned;
5724     case 'd':
5725       return eFormatDecimal;
5726     case 'D':
5727       return eFormatDefault;
5728     case 'i':
5729       return eFormatInstruction;
5730     case 'A':
5731       return eFormatAddressInfo;
5732     case 'p':
5733       return eFormatPointer;
5734     case 'c':
5735       return eFormatChar;
5736     case 'b':
5737       return eFormatBinary;
5738     case 'B':
5739       return eFormatBytesWithASCII;
5740     case 'f':
5741       return eFormatFloat;
5742     }
5743     return eFormatDefault;
5744   }
5745 
DisplayRowObject(Window & window,Row & row,DisplayOptions & options,bool highlight,bool last_child)5746   bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options,
5747                         bool highlight, bool last_child) {
5748     ValueObject *valobj = row.value.GetSP().get();
5749 
5750     if (valobj == nullptr)
5751       return false;
5752 
5753     const char *type_name =
5754         options.show_types ? valobj->GetTypeName().GetCString() : nullptr;
5755     const char *name = valobj->GetName().GetCString();
5756     const char *value = valobj->GetValueAsCString();
5757     const char *summary = valobj->GetSummaryAsCString();
5758 
5759     window.MoveCursor(row.x, row.y);
5760 
5761     row.DrawTree(window);
5762 
5763     if (highlight)
5764       window.AttributeOn(A_REVERSE);
5765 
5766     if (type_name && type_name[0])
5767       window.PrintfTruncated(1, "(%s) ", type_name);
5768 
5769     if (name && name[0])
5770       window.PutCStringTruncated(1, name);
5771 
5772     attr_t changd_attr = 0;
5773     if (valobj->GetValueDidChange())
5774       changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD;
5775 
5776     if (value && value[0]) {
5777       window.PutCStringTruncated(1, " = ");
5778       if (changd_attr)
5779         window.AttributeOn(changd_attr);
5780       window.PutCStringTruncated(1, value);
5781       if (changd_attr)
5782         window.AttributeOff(changd_attr);
5783     }
5784 
5785     if (summary && summary[0]) {
5786       window.PutCStringTruncated(1, " ");
5787       if (changd_attr)
5788         window.AttributeOn(changd_attr);
5789       window.PutCStringTruncated(1, summary);
5790       if (changd_attr)
5791         window.AttributeOff(changd_attr);
5792     }
5793 
5794     if (highlight)
5795       window.AttributeOff(A_REVERSE);
5796 
5797     return true;
5798   }
5799 
DisplayRows(Window & window,std::vector<Row> & rows,DisplayOptions & options)5800   void DisplayRows(Window &window, std::vector<Row> &rows,
5801                    DisplayOptions &options) {
5802     // >   0x25B7
5803     // \/  0x25BD
5804 
5805     bool window_is_active = window.IsActive();
5806     for (auto &row : rows) {
5807       const bool last_child = row.parent && &rows[rows.size() - 1] == &row;
5808       // Save the row index in each Row structure
5809       row.row_idx = m_num_rows;
5810       if ((m_num_rows >= m_first_visible_row) &&
5811           ((m_num_rows - m_first_visible_row) <
5812            static_cast<size_t>(NumVisibleRows()))) {
5813         row.x = m_min_x;
5814         row.y = m_num_rows - m_first_visible_row + 1;
5815         if (DisplayRowObject(window, row, options,
5816                              window_is_active &&
5817                                  m_num_rows == m_selected_row_idx,
5818                              last_child)) {
5819           ++m_num_rows;
5820         } else {
5821           row.x = 0;
5822           row.y = 0;
5823         }
5824       } else {
5825         row.x = 0;
5826         row.y = 0;
5827         ++m_num_rows;
5828       }
5829 
5830       if (row.expanded) {
5831         auto &children = row.GetChildren();
5832         if (!children.empty()) {
5833           DisplayRows(window, children, options);
5834         }
5835       }
5836     }
5837   }
5838 
CalculateTotalNumberRows(std::vector<Row> & rows)5839   int CalculateTotalNumberRows(std::vector<Row> &rows) {
5840     int row_count = 0;
5841     for (auto &row : rows) {
5842       ++row_count;
5843       if (row.expanded)
5844         row_count += CalculateTotalNumberRows(row.GetChildren());
5845     }
5846     return row_count;
5847   }
5848 
GetRowForRowIndexImpl(std::vector<Row> & rows,size_t & row_index)5849   static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) {
5850     for (auto &row : rows) {
5851       if (row_index == 0)
5852         return &row;
5853       else {
5854         --row_index;
5855         if (row.expanded) {
5856           auto &children = row.GetChildren();
5857           if (!children.empty()) {
5858             Row *result = GetRowForRowIndexImpl(children, row_index);
5859             if (result)
5860               return result;
5861           }
5862         }
5863       }
5864     }
5865     return nullptr;
5866   }
5867 
GetRowForRowIndex(size_t row_index)5868   Row *GetRowForRowIndex(size_t row_index) {
5869     return GetRowForRowIndexImpl(m_rows, row_index);
5870   }
5871 
NumVisibleRows() const5872   int NumVisibleRows() const { return m_max_y - m_min_y; }
5873 
5874   static DisplayOptions g_options;
5875 };
5876 
5877 class FrameVariablesWindowDelegate : public ValueObjectListDelegate {
5878 public:
FrameVariablesWindowDelegate(Debugger & debugger)5879   FrameVariablesWindowDelegate(Debugger &debugger)
5880       : ValueObjectListDelegate(), m_debugger(debugger) {}
5881 
5882   ~FrameVariablesWindowDelegate() override = default;
5883 
WindowDelegateGetHelpText()5884   const char *WindowDelegateGetHelpText() override {
5885     return "Frame variable window keyboard shortcuts:";
5886   }
5887 
WindowDelegateDraw(Window & window,bool force)5888   bool WindowDelegateDraw(Window &window, bool force) override {
5889     ExecutionContext exe_ctx(
5890         m_debugger.GetCommandInterpreter().GetExecutionContext());
5891     Process *process = exe_ctx.GetProcessPtr();
5892     Block *frame_block = nullptr;
5893     StackFrame *frame = nullptr;
5894 
5895     if (process) {
5896       StateType state = process->GetState();
5897       if (StateIsStoppedState(state, true)) {
5898         frame = exe_ctx.GetFramePtr();
5899         if (frame)
5900           frame_block = frame->GetFrameBlock();
5901       } else if (StateIsRunningState(state)) {
5902         return true; // Don't do any updating when we are running
5903       }
5904     }
5905 
5906     ValueObjectList local_values;
5907     if (frame_block) {
5908       // Only update the variables if they have changed
5909       if (m_frame_block != frame_block) {
5910         m_frame_block = frame_block;
5911 
5912         VariableList *locals = frame->GetVariableList(true, nullptr);
5913         if (locals) {
5914           const DynamicValueType use_dynamic = eDynamicDontRunTarget;
5915           for (const VariableSP &local_sp : *locals) {
5916             ValueObjectSP value_sp =
5917                 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic);
5918             if (value_sp) {
5919               ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue();
5920               if (synthetic_value_sp)
5921                 local_values.Append(synthetic_value_sp);
5922               else
5923                 local_values.Append(value_sp);
5924             }
5925           }
5926           // Update the values
5927           SetValues(local_values);
5928         }
5929       }
5930     } else {
5931       m_frame_block = nullptr;
5932       // Update the values with an empty list if there is no frame
5933       SetValues(local_values);
5934     }
5935 
5936     return ValueObjectListDelegate::WindowDelegateDraw(window, force);
5937   }
5938 
5939 protected:
5940   Debugger &m_debugger;
5941   Block *m_frame_block = nullptr;
5942 };
5943 
5944 class RegistersWindowDelegate : public ValueObjectListDelegate {
5945 public:
RegistersWindowDelegate(Debugger & debugger)5946   RegistersWindowDelegate(Debugger &debugger)
5947       : ValueObjectListDelegate(), m_debugger(debugger) {}
5948 
5949   ~RegistersWindowDelegate() override = default;
5950 
WindowDelegateGetHelpText()5951   const char *WindowDelegateGetHelpText() override {
5952     return "Register window keyboard shortcuts:";
5953   }
5954 
WindowDelegateDraw(Window & window,bool force)5955   bool WindowDelegateDraw(Window &window, bool force) override {
5956     ExecutionContext exe_ctx(
5957         m_debugger.GetCommandInterpreter().GetExecutionContext());
5958     StackFrame *frame = exe_ctx.GetFramePtr();
5959 
5960     ValueObjectList value_list;
5961     if (frame) {
5962       if (frame->GetStackID() != m_stack_id) {
5963         m_stack_id = frame->GetStackID();
5964         RegisterContextSP reg_ctx(frame->GetRegisterContext());
5965         if (reg_ctx) {
5966           const uint32_t num_sets = reg_ctx->GetRegisterSetCount();
5967           for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) {
5968             value_list.Append(
5969                 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx));
5970           }
5971         }
5972         SetValues(value_list);
5973       }
5974     } else {
5975       Process *process = exe_ctx.GetProcessPtr();
5976       if (process && process->IsAlive())
5977         return true; // Don't do any updating if we are running
5978       else {
5979         // Update the values with an empty list if there is no process or the
5980         // process isn't alive anymore
5981         SetValues(value_list);
5982       }
5983     }
5984     return ValueObjectListDelegate::WindowDelegateDraw(window, force);
5985   }
5986 
5987 protected:
5988   Debugger &m_debugger;
5989   StackID m_stack_id;
5990 };
5991 
CursesKeyToCString(int ch)5992 static const char *CursesKeyToCString(int ch) {
5993   static char g_desc[32];
5994   if (ch >= KEY_F0 && ch < KEY_F0 + 64) {
5995     snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0);
5996     return g_desc;
5997   }
5998   switch (ch) {
5999   case KEY_DOWN:
6000     return "down";
6001   case KEY_UP:
6002     return "up";
6003   case KEY_LEFT:
6004     return "left";
6005   case KEY_RIGHT:
6006     return "right";
6007   case KEY_HOME:
6008     return "home";
6009   case KEY_BACKSPACE:
6010     return "backspace";
6011   case KEY_DL:
6012     return "delete-line";
6013   case KEY_IL:
6014     return "insert-line";
6015   case KEY_DC:
6016     return "delete-char";
6017   case KEY_IC:
6018     return "insert-char";
6019   case KEY_CLEAR:
6020     return "clear";
6021   case KEY_EOS:
6022     return "clear-to-eos";
6023   case KEY_EOL:
6024     return "clear-to-eol";
6025   case KEY_SF:
6026     return "scroll-forward";
6027   case KEY_SR:
6028     return "scroll-backward";
6029   case KEY_NPAGE:
6030     return "page-down";
6031   case KEY_PPAGE:
6032     return "page-up";
6033   case KEY_STAB:
6034     return "set-tab";
6035   case KEY_CTAB:
6036     return "clear-tab";
6037   case KEY_CATAB:
6038     return "clear-all-tabs";
6039   case KEY_ENTER:
6040     return "enter";
6041   case KEY_PRINT:
6042     return "print";
6043   case KEY_LL:
6044     return "lower-left key";
6045   case KEY_A1:
6046     return "upper left of keypad";
6047   case KEY_A3:
6048     return "upper right of keypad";
6049   case KEY_B2:
6050     return "center of keypad";
6051   case KEY_C1:
6052     return "lower left of keypad";
6053   case KEY_C3:
6054     return "lower right of keypad";
6055   case KEY_BTAB:
6056     return "back-tab key";
6057   case KEY_BEG:
6058     return "begin key";
6059   case KEY_CANCEL:
6060     return "cancel key";
6061   case KEY_CLOSE:
6062     return "close key";
6063   case KEY_COMMAND:
6064     return "command key";
6065   case KEY_COPY:
6066     return "copy key";
6067   case KEY_CREATE:
6068     return "create key";
6069   case KEY_END:
6070     return "end key";
6071   case KEY_EXIT:
6072     return "exit key";
6073   case KEY_FIND:
6074     return "find key";
6075   case KEY_HELP:
6076     return "help key";
6077   case KEY_MARK:
6078     return "mark key";
6079   case KEY_MESSAGE:
6080     return "message key";
6081   case KEY_MOVE:
6082     return "move key";
6083   case KEY_NEXT:
6084     return "next key";
6085   case KEY_OPEN:
6086     return "open key";
6087   case KEY_OPTIONS:
6088     return "options key";
6089   case KEY_PREVIOUS:
6090     return "previous key";
6091   case KEY_REDO:
6092     return "redo key";
6093   case KEY_REFERENCE:
6094     return "reference key";
6095   case KEY_REFRESH:
6096     return "refresh key";
6097   case KEY_REPLACE:
6098     return "replace key";
6099   case KEY_RESTART:
6100     return "restart key";
6101   case KEY_RESUME:
6102     return "resume key";
6103   case KEY_SAVE:
6104     return "save key";
6105   case KEY_SBEG:
6106     return "shifted begin key";
6107   case KEY_SCANCEL:
6108     return "shifted cancel key";
6109   case KEY_SCOMMAND:
6110     return "shifted command key";
6111   case KEY_SCOPY:
6112     return "shifted copy key";
6113   case KEY_SCREATE:
6114     return "shifted create key";
6115   case KEY_SDC:
6116     return "shifted delete-character key";
6117   case KEY_SDL:
6118     return "shifted delete-line key";
6119   case KEY_SELECT:
6120     return "select key";
6121   case KEY_SEND:
6122     return "shifted end key";
6123   case KEY_SEOL:
6124     return "shifted clear-to-end-of-line key";
6125   case KEY_SEXIT:
6126     return "shifted exit key";
6127   case KEY_SFIND:
6128     return "shifted find key";
6129   case KEY_SHELP:
6130     return "shifted help key";
6131   case KEY_SHOME:
6132     return "shifted home key";
6133   case KEY_SIC:
6134     return "shifted insert-character key";
6135   case KEY_SLEFT:
6136     return "shifted left-arrow key";
6137   case KEY_SMESSAGE:
6138     return "shifted message key";
6139   case KEY_SMOVE:
6140     return "shifted move key";
6141   case KEY_SNEXT:
6142     return "shifted next key";
6143   case KEY_SOPTIONS:
6144     return "shifted options key";
6145   case KEY_SPREVIOUS:
6146     return "shifted previous key";
6147   case KEY_SPRINT:
6148     return "shifted print key";
6149   case KEY_SREDO:
6150     return "shifted redo key";
6151   case KEY_SREPLACE:
6152     return "shifted replace key";
6153   case KEY_SRIGHT:
6154     return "shifted right-arrow key";
6155   case KEY_SRSUME:
6156     return "shifted resume key";
6157   case KEY_SSAVE:
6158     return "shifted save key";
6159   case KEY_SSUSPEND:
6160     return "shifted suspend key";
6161   case KEY_SUNDO:
6162     return "shifted undo key";
6163   case KEY_SUSPEND:
6164     return "suspend key";
6165   case KEY_UNDO:
6166     return "undo key";
6167   case KEY_MOUSE:
6168     return "Mouse event has occurred";
6169   case KEY_RESIZE:
6170     return "Terminal resize event";
6171 #ifdef KEY_EVENT
6172   case KEY_EVENT:
6173     return "We were interrupted by an event";
6174 #endif
6175   case KEY_RETURN:
6176     return "return";
6177   case ' ':
6178     return "space";
6179   case '\t':
6180     return "tab";
6181   case KEY_ESCAPE:
6182     return "escape";
6183   default:
6184     if (llvm::isPrint(ch))
6185       snprintf(g_desc, sizeof(g_desc), "%c", ch);
6186     else
6187       snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch);
6188     return g_desc;
6189   }
6190   return nullptr;
6191 }
6192 
HelpDialogDelegate(const char * text,KeyHelp * key_help_array)6193 HelpDialogDelegate::HelpDialogDelegate(const char *text,
6194                                        KeyHelp *key_help_array)
6195     : m_text() {
6196   if (text && text[0]) {
6197     m_text.SplitIntoLines(text);
6198     m_text.AppendString("");
6199   }
6200   if (key_help_array) {
6201     for (KeyHelp *key = key_help_array; key->ch; ++key) {
6202       StreamString key_description;
6203       key_description.Printf("%10s - %s", CursesKeyToCString(key->ch),
6204                              key->description);
6205       m_text.AppendString(key_description.GetString());
6206     }
6207   }
6208 }
6209 
6210 HelpDialogDelegate::~HelpDialogDelegate() = default;
6211 
WindowDelegateDraw(Window & window,bool force)6212 bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) {
6213   window.Erase();
6214   const int window_height = window.GetHeight();
6215   int x = 2;
6216   int y = 1;
6217   const int min_y = y;
6218   const int max_y = window_height - 1 - y;
6219   const size_t num_visible_lines = max_y - min_y + 1;
6220   const size_t num_lines = m_text.GetSize();
6221   const char *bottom_message;
6222   if (num_lines <= num_visible_lines)
6223     bottom_message = "Press any key to exit";
6224   else
6225     bottom_message = "Use arrows to scroll, any other key to exit";
6226   window.DrawTitleBox(window.GetName(), bottom_message);
6227   while (y <= max_y) {
6228     window.MoveCursor(x, y);
6229     window.PutCStringTruncated(
6230         1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y));
6231     ++y;
6232   }
6233   return true;
6234 }
6235 
WindowDelegateHandleChar(Window & window,int key)6236 HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window,
6237                                                               int key) {
6238   bool done = false;
6239   const size_t num_lines = m_text.GetSize();
6240   const size_t num_visible_lines = window.GetHeight() - 2;
6241 
6242   if (num_lines <= num_visible_lines) {
6243     done = true;
6244     // If we have all lines visible and don't need scrolling, then any key
6245     // press will cause us to exit
6246   } else {
6247     switch (key) {
6248     case KEY_UP:
6249       if (m_first_visible_line > 0)
6250         --m_first_visible_line;
6251       break;
6252 
6253     case KEY_DOWN:
6254       if (m_first_visible_line + num_visible_lines < num_lines)
6255         ++m_first_visible_line;
6256       break;
6257 
6258     case KEY_PPAGE:
6259     case ',':
6260       if (m_first_visible_line > 0) {
6261         if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines)
6262           m_first_visible_line -= num_visible_lines;
6263         else
6264           m_first_visible_line = 0;
6265       }
6266       break;
6267 
6268     case KEY_NPAGE:
6269     case '.':
6270       if (m_first_visible_line + num_visible_lines < num_lines) {
6271         m_first_visible_line += num_visible_lines;
6272         if (static_cast<size_t>(m_first_visible_line) > num_lines)
6273           m_first_visible_line = num_lines - num_visible_lines;
6274       }
6275       break;
6276 
6277     default:
6278       done = true;
6279       break;
6280     }
6281   }
6282   if (done)
6283     window.GetParent()->RemoveSubWindow(&window);
6284   return eKeyHandled;
6285 }
6286 
6287 class ApplicationDelegate : public WindowDelegate, public MenuDelegate {
6288 public:
6289   enum {
6290     eMenuID_LLDB = 1,
6291     eMenuID_LLDBAbout,
6292     eMenuID_LLDBExit,
6293 
6294     eMenuID_Target,
6295     eMenuID_TargetCreate,
6296     eMenuID_TargetDelete,
6297 
6298     eMenuID_Process,
6299     eMenuID_ProcessAttach,
6300     eMenuID_ProcessDetachResume,
6301     eMenuID_ProcessDetachSuspended,
6302     eMenuID_ProcessLaunch,
6303     eMenuID_ProcessContinue,
6304     eMenuID_ProcessHalt,
6305     eMenuID_ProcessKill,
6306 
6307     eMenuID_Thread,
6308     eMenuID_ThreadStepIn,
6309     eMenuID_ThreadStepOver,
6310     eMenuID_ThreadStepOut,
6311 
6312     eMenuID_View,
6313     eMenuID_ViewBacktrace,
6314     eMenuID_ViewRegisters,
6315     eMenuID_ViewSource,
6316     eMenuID_ViewVariables,
6317     eMenuID_ViewBreakpoints,
6318 
6319     eMenuID_Help,
6320     eMenuID_HelpGUIHelp
6321   };
6322 
ApplicationDelegate(Application & app,Debugger & debugger)6323   ApplicationDelegate(Application &app, Debugger &debugger)
6324       : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {}
6325 
6326   ~ApplicationDelegate() override = default;
6327 
WindowDelegateDraw(Window & window,bool force)6328   bool WindowDelegateDraw(Window &window, bool force) override {
6329     return false; // Drawing not handled, let standard window drawing happen
6330   }
6331 
WindowDelegateHandleChar(Window & window,int key)6332   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
6333     switch (key) {
6334     case '\t':
6335       window.SelectNextWindowAsActive();
6336       return eKeyHandled;
6337 
6338     case KEY_SHIFT_TAB:
6339       window.SelectPreviousWindowAsActive();
6340       return eKeyHandled;
6341 
6342     case 'h':
6343       window.CreateHelpSubwindow();
6344       return eKeyHandled;
6345 
6346     case KEY_ESCAPE:
6347       return eQuitApplication;
6348 
6349     default:
6350       break;
6351     }
6352     return eKeyNotHandled;
6353   }
6354 
WindowDelegateGetHelpText()6355   const char *WindowDelegateGetHelpText() override {
6356     return "Welcome to the LLDB curses GUI.\n\n"
6357            "Press the TAB key to change the selected view.\n"
6358            "Each view has its own keyboard shortcuts, press 'h' to open a "
6359            "dialog to display them.\n\n"
6360            "Common key bindings for all views:";
6361   }
6362 
WindowDelegateGetKeyHelp()6363   KeyHelp *WindowDelegateGetKeyHelp() override {
6364     static curses::KeyHelp g_source_view_key_help[] = {
6365         {'\t', "Select next view"},
6366         {KEY_BTAB, "Select previous view"},
6367         {'h', "Show help dialog with view specific key bindings"},
6368         {',', "Page up"},
6369         {'.', "Page down"},
6370         {KEY_UP, "Select previous"},
6371         {KEY_DOWN, "Select next"},
6372         {KEY_LEFT, "Unexpand or select parent"},
6373         {KEY_RIGHT, "Expand"},
6374         {KEY_PPAGE, "Page up"},
6375         {KEY_NPAGE, "Page down"},
6376         {'\0', nullptr}};
6377     return g_source_view_key_help;
6378   }
6379 
MenuDelegateAction(Menu & menu)6380   MenuActionResult MenuDelegateAction(Menu &menu) override {
6381     switch (menu.GetIdentifier()) {
6382     case eMenuID_TargetCreate: {
6383       WindowSP main_window_sp = m_app.GetMainWindow();
6384       FormDelegateSP form_delegate_sp =
6385           FormDelegateSP(new TargetCreateFormDelegate(m_debugger));
6386       Rect bounds = main_window_sp->GetCenteredRect(80, 19);
6387       WindowSP form_window_sp = main_window_sp->CreateSubWindow(
6388           form_delegate_sp->GetName().c_str(), bounds, true);
6389       WindowDelegateSP window_delegate_sp =
6390           WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
6391       form_window_sp->SetDelegate(window_delegate_sp);
6392       return MenuActionResult::Handled;
6393     }
6394     case eMenuID_ThreadStepIn: {
6395       ExecutionContext exe_ctx =
6396           m_debugger.GetCommandInterpreter().GetExecutionContext();
6397       if (exe_ctx.HasThreadScope()) {
6398         Process *process = exe_ctx.GetProcessPtr();
6399         if (process && process->IsAlive() &&
6400             StateIsStoppedState(process->GetState(), true))
6401           exe_ctx.GetThreadRef().StepIn(true);
6402       }
6403     }
6404       return MenuActionResult::Handled;
6405 
6406     case eMenuID_ThreadStepOut: {
6407       ExecutionContext exe_ctx =
6408           m_debugger.GetCommandInterpreter().GetExecutionContext();
6409       if (exe_ctx.HasThreadScope()) {
6410         Process *process = exe_ctx.GetProcessPtr();
6411         if (process && process->IsAlive() &&
6412             StateIsStoppedState(process->GetState(), true)) {
6413           Thread *thread = exe_ctx.GetThreadPtr();
6414           uint32_t frame_idx = thread->GetSelectedFrameIndex();
6415           exe_ctx.GetThreadRef().StepOut(frame_idx);
6416         }
6417       }
6418     }
6419       return MenuActionResult::Handled;
6420 
6421     case eMenuID_ThreadStepOver: {
6422       ExecutionContext exe_ctx =
6423           m_debugger.GetCommandInterpreter().GetExecutionContext();
6424       if (exe_ctx.HasThreadScope()) {
6425         Process *process = exe_ctx.GetProcessPtr();
6426         if (process && process->IsAlive() &&
6427             StateIsStoppedState(process->GetState(), true))
6428           exe_ctx.GetThreadRef().StepOver(true);
6429       }
6430     }
6431       return MenuActionResult::Handled;
6432 
6433     case eMenuID_ProcessAttach: {
6434       WindowSP main_window_sp = m_app.GetMainWindow();
6435       FormDelegateSP form_delegate_sp = FormDelegateSP(
6436           new ProcessAttachFormDelegate(m_debugger, main_window_sp));
6437       Rect bounds = main_window_sp->GetCenteredRect(80, 22);
6438       WindowSP form_window_sp = main_window_sp->CreateSubWindow(
6439           form_delegate_sp->GetName().c_str(), bounds, true);
6440       WindowDelegateSP window_delegate_sp =
6441           WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
6442       form_window_sp->SetDelegate(window_delegate_sp);
6443       return MenuActionResult::Handled;
6444     }
6445     case eMenuID_ProcessLaunch: {
6446       WindowSP main_window_sp = m_app.GetMainWindow();
6447       FormDelegateSP form_delegate_sp = FormDelegateSP(
6448           new ProcessLaunchFormDelegate(m_debugger, main_window_sp));
6449       Rect bounds = main_window_sp->GetCenteredRect(80, 22);
6450       WindowSP form_window_sp = main_window_sp->CreateSubWindow(
6451           form_delegate_sp->GetName().c_str(), bounds, true);
6452       WindowDelegateSP window_delegate_sp =
6453           WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
6454       form_window_sp->SetDelegate(window_delegate_sp);
6455       return MenuActionResult::Handled;
6456     }
6457 
6458     case eMenuID_ProcessContinue: {
6459       ExecutionContext exe_ctx =
6460           m_debugger.GetCommandInterpreter().GetExecutionContext();
6461       if (exe_ctx.HasProcessScope()) {
6462         Process *process = exe_ctx.GetProcessPtr();
6463         if (process && process->IsAlive() &&
6464             StateIsStoppedState(process->GetState(), true))
6465           process->Resume();
6466       }
6467     }
6468       return MenuActionResult::Handled;
6469 
6470     case eMenuID_ProcessKill: {
6471       ExecutionContext exe_ctx =
6472           m_debugger.GetCommandInterpreter().GetExecutionContext();
6473       if (exe_ctx.HasProcessScope()) {
6474         Process *process = exe_ctx.GetProcessPtr();
6475         if (process && process->IsAlive())
6476           process->Destroy(false);
6477       }
6478     }
6479       return MenuActionResult::Handled;
6480 
6481     case eMenuID_ProcessHalt: {
6482       ExecutionContext exe_ctx =
6483           m_debugger.GetCommandInterpreter().GetExecutionContext();
6484       if (exe_ctx.HasProcessScope()) {
6485         Process *process = exe_ctx.GetProcessPtr();
6486         if (process && process->IsAlive())
6487           process->Halt();
6488       }
6489     }
6490       return MenuActionResult::Handled;
6491 
6492     case eMenuID_ProcessDetachResume:
6493     case eMenuID_ProcessDetachSuspended: {
6494       ExecutionContext exe_ctx =
6495           m_debugger.GetCommandInterpreter().GetExecutionContext();
6496       if (exe_ctx.HasProcessScope()) {
6497         Process *process = exe_ctx.GetProcessPtr();
6498         if (process && process->IsAlive())
6499           process->Detach(menu.GetIdentifier() ==
6500                           eMenuID_ProcessDetachSuspended);
6501       }
6502     }
6503       return MenuActionResult::Handled;
6504 
6505     case eMenuID_Process: {
6506       // Populate the menu with all of the threads if the process is stopped
6507       // when the Process menu gets selected and is about to display its
6508       // submenu.
6509       Menus &submenus = menu.GetSubmenus();
6510       ExecutionContext exe_ctx =
6511           m_debugger.GetCommandInterpreter().GetExecutionContext();
6512       Process *process = exe_ctx.GetProcessPtr();
6513       if (process && process->IsAlive() &&
6514           StateIsStoppedState(process->GetState(), true)) {
6515         if (submenus.size() == 7)
6516           menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
6517         else if (submenus.size() > 8)
6518           submenus.erase(submenus.begin() + 8, submenus.end());
6519 
6520         ThreadList &threads = process->GetThreadList();
6521         std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
6522         size_t num_threads = threads.GetSize();
6523         for (size_t i = 0; i < num_threads; ++i) {
6524           ThreadSP thread_sp = threads.GetThreadAtIndex(i);
6525           char menu_char = '\0';
6526           if (i < 9)
6527             menu_char = '1' + i;
6528           StreamString thread_menu_title;
6529           thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID());
6530           const char *thread_name = thread_sp->GetName();
6531           if (thread_name && thread_name[0])
6532             thread_menu_title.Printf(" %s", thread_name);
6533           else {
6534             const char *queue_name = thread_sp->GetQueueName();
6535             if (queue_name && queue_name[0])
6536               thread_menu_title.Printf(" %s", queue_name);
6537           }
6538           menu.AddSubmenu(
6539               MenuSP(new Menu(thread_menu_title.GetString().str().c_str(),
6540                               nullptr, menu_char, thread_sp->GetID())));
6541         }
6542       } else if (submenus.size() > 7) {
6543         // Remove the separator and any other thread submenu items that were
6544         // previously added
6545         submenus.erase(submenus.begin() + 7, submenus.end());
6546       }
6547       // Since we are adding and removing items we need to recalculate the
6548       // name lengths
6549       menu.RecalculateNameLengths();
6550     }
6551       return MenuActionResult::Handled;
6552 
6553     case eMenuID_ViewVariables: {
6554       WindowSP main_window_sp = m_app.GetMainWindow();
6555       WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
6556       WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
6557       WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
6558       const Rect source_bounds = source_window_sp->GetBounds();
6559 
6560       if (variables_window_sp) {
6561         const Rect variables_bounds = variables_window_sp->GetBounds();
6562 
6563         main_window_sp->RemoveSubWindow(variables_window_sp.get());
6564 
6565         if (registers_window_sp) {
6566           // We have a registers window, so give all the area back to the
6567           // registers window
6568           Rect registers_bounds = variables_bounds;
6569           registers_bounds.size.width = source_bounds.size.width;
6570           registers_window_sp->SetBounds(registers_bounds);
6571         } else {
6572           // We have no registers window showing so give the bottom area back
6573           // to the source view
6574           source_window_sp->Resize(source_bounds.size.width,
6575                                    source_bounds.size.height +
6576                                        variables_bounds.size.height);
6577         }
6578       } else {
6579         Rect new_variables_rect;
6580         if (registers_window_sp) {
6581           // We have a registers window so split the area of the registers
6582           // window into two columns where the left hand side will be the
6583           // variables and the right hand side will be the registers
6584           const Rect variables_bounds = registers_window_sp->GetBounds();
6585           Rect new_registers_rect;
6586           variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect,
6587                                                    new_registers_rect);
6588           registers_window_sp->SetBounds(new_registers_rect);
6589         } else {
6590           // No registers window, grab the bottom part of the source window
6591           Rect new_source_rect;
6592           source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
6593                                                   new_variables_rect);
6594           source_window_sp->SetBounds(new_source_rect);
6595         }
6596         WindowSP new_window_sp = main_window_sp->CreateSubWindow(
6597             "Variables", new_variables_rect, false);
6598         new_window_sp->SetDelegate(
6599             WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
6600       }
6601       touchwin(stdscr);
6602     }
6603       return MenuActionResult::Handled;
6604 
6605     case eMenuID_ViewRegisters: {
6606       WindowSP main_window_sp = m_app.GetMainWindow();
6607       WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
6608       WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
6609       WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
6610       const Rect source_bounds = source_window_sp->GetBounds();
6611 
6612       if (registers_window_sp) {
6613         if (variables_window_sp) {
6614           const Rect variables_bounds = variables_window_sp->GetBounds();
6615 
6616           // We have a variables window, so give all the area back to the
6617           // variables window
6618           variables_window_sp->Resize(variables_bounds.size.width +
6619                                           registers_window_sp->GetWidth(),
6620                                       variables_bounds.size.height);
6621         } else {
6622           // We have no variables window showing so give the bottom area back
6623           // to the source view
6624           source_window_sp->Resize(source_bounds.size.width,
6625                                    source_bounds.size.height +
6626                                        registers_window_sp->GetHeight());
6627         }
6628         main_window_sp->RemoveSubWindow(registers_window_sp.get());
6629       } else {
6630         Rect new_regs_rect;
6631         if (variables_window_sp) {
6632           // We have a variables window, split it into two columns where the
6633           // left hand side will be the variables and the right hand side will
6634           // be the registers
6635           const Rect variables_bounds = variables_window_sp->GetBounds();
6636           Rect new_vars_rect;
6637           variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect,
6638                                                    new_regs_rect);
6639           variables_window_sp->SetBounds(new_vars_rect);
6640         } else {
6641           // No variables window, grab the bottom part of the source window
6642           Rect new_source_rect;
6643           source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
6644                                                   new_regs_rect);
6645           source_window_sp->SetBounds(new_source_rect);
6646         }
6647         WindowSP new_window_sp =
6648             main_window_sp->CreateSubWindow("Registers", new_regs_rect, false);
6649         new_window_sp->SetDelegate(
6650             WindowDelegateSP(new RegistersWindowDelegate(m_debugger)));
6651       }
6652       touchwin(stdscr);
6653     }
6654       return MenuActionResult::Handled;
6655 
6656     case eMenuID_ViewBreakpoints: {
6657       WindowSP main_window_sp = m_app.GetMainWindow();
6658       WindowSP threads_window_sp = main_window_sp->FindSubWindow("Threads");
6659       WindowSP breakpoints_window_sp =
6660           main_window_sp->FindSubWindow("Breakpoints");
6661       const Rect threads_bounds = threads_window_sp->GetBounds();
6662 
6663       // If a breakpoints window already exists, remove it and give the area
6664       // it used to occupy to the threads window. If it doesn't exist, split
6665       // the threads window horizontally into two windows where the top window
6666       // is the threads window and the bottom window is a newly added
6667       // breakpoints window.
6668       if (breakpoints_window_sp) {
6669         threads_window_sp->Resize(threads_bounds.size.width,
6670                                   threads_bounds.size.height +
6671                                       breakpoints_window_sp->GetHeight());
6672         main_window_sp->RemoveSubWindow(breakpoints_window_sp.get());
6673       } else {
6674         Rect new_threads_bounds, breakpoints_bounds;
6675         threads_bounds.HorizontalSplitPercentage(0.70, new_threads_bounds,
6676                                                  breakpoints_bounds);
6677         threads_window_sp->SetBounds(new_threads_bounds);
6678         breakpoints_window_sp = main_window_sp->CreateSubWindow(
6679             "Breakpoints", breakpoints_bounds, false);
6680         TreeDelegateSP breakpoints_delegate_sp(
6681             new BreakpointsTreeDelegate(m_debugger));
6682         breakpoints_window_sp->SetDelegate(WindowDelegateSP(
6683             new TreeWindowDelegate(m_debugger, breakpoints_delegate_sp)));
6684       }
6685       touchwin(stdscr);
6686       return MenuActionResult::Handled;
6687     }
6688 
6689     case eMenuID_HelpGUIHelp:
6690       m_app.GetMainWindow()->CreateHelpSubwindow();
6691       return MenuActionResult::Handled;
6692 
6693     default:
6694       break;
6695     }
6696 
6697     return MenuActionResult::NotHandled;
6698   }
6699 
6700 protected:
6701   Application &m_app;
6702   Debugger &m_debugger;
6703 };
6704 
6705 class StatusBarWindowDelegate : public WindowDelegate {
6706 public:
StatusBarWindowDelegate(Debugger & debugger)6707   StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) {
6708     FormatEntity::Parse("Thread: ${thread.id%tid}", m_format);
6709   }
6710 
6711   ~StatusBarWindowDelegate() override = default;
6712 
WindowDelegateDraw(Window & window,bool force)6713   bool WindowDelegateDraw(Window &window, bool force) override {
6714     ExecutionContext exe_ctx =
6715         m_debugger.GetCommandInterpreter().GetExecutionContext();
6716     Process *process = exe_ctx.GetProcessPtr();
6717     Thread *thread = exe_ctx.GetThreadPtr();
6718     StackFrame *frame = exe_ctx.GetFramePtr();
6719     window.Erase();
6720     window.SetBackground(BlackOnWhite);
6721     window.MoveCursor(0, 0);
6722     if (process) {
6723       const StateType state = process->GetState();
6724       window.Printf("Process: %5" PRIu64 " %10s", process->GetID(),
6725                     StateAsCString(state));
6726 
6727       if (StateIsStoppedState(state, true)) {
6728         StreamString strm;
6729         if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx,
6730                                            nullptr, nullptr, false, false)) {
6731           window.MoveCursor(40, 0);
6732           window.PutCStringTruncated(1, strm.GetString().str().c_str());
6733         }
6734 
6735         window.MoveCursor(60, 0);
6736         if (frame)
6737           window.Printf("Frame: %3u  PC = 0x%16.16" PRIx64,
6738                         frame->GetFrameIndex(),
6739                         frame->GetFrameCodeAddress().GetOpcodeLoadAddress(
6740                             exe_ctx.GetTargetPtr()));
6741       } else if (state == eStateExited) {
6742         const char *exit_desc = process->GetExitDescription();
6743         const int exit_status = process->GetExitStatus();
6744         if (exit_desc && exit_desc[0])
6745           window.Printf(" with status = %i (%s)", exit_status, exit_desc);
6746         else
6747           window.Printf(" with status = %i", exit_status);
6748       }
6749     }
6750     return true;
6751   }
6752 
6753 protected:
6754   Debugger &m_debugger;
6755   FormatEntity::Entry m_format;
6756 };
6757 
6758 class SourceFileWindowDelegate : public WindowDelegate {
6759 public:
SourceFileWindowDelegate(Debugger & debugger)6760   SourceFileWindowDelegate(Debugger &debugger)
6761       : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(),
6762         m_disassembly_sp(), m_disassembly_range(), m_title() {}
6763 
6764   ~SourceFileWindowDelegate() override = default;
6765 
Update(const SymbolContext & sc)6766   void Update(const SymbolContext &sc) { m_sc = sc; }
6767 
NumVisibleLines() const6768   uint32_t NumVisibleLines() const { return m_max_y - m_min_y; }
6769 
WindowDelegateGetHelpText()6770   const char *WindowDelegateGetHelpText() override {
6771     return "Source/Disassembly window keyboard shortcuts:";
6772   }
6773 
WindowDelegateGetKeyHelp()6774   KeyHelp *WindowDelegateGetKeyHelp() override {
6775     static curses::KeyHelp g_source_view_key_help[] = {
6776         {KEY_RETURN, "Run to selected line with one shot breakpoint"},
6777         {KEY_UP, "Select previous source line"},
6778         {KEY_DOWN, "Select next source line"},
6779         {KEY_LEFT, "Scroll to the left"},
6780         {KEY_RIGHT, "Scroll to the right"},
6781         {KEY_PPAGE, "Page up"},
6782         {KEY_NPAGE, "Page down"},
6783         {'b', "Set breakpoint on selected source/disassembly line"},
6784         {'c', "Continue process"},
6785         {'D', "Detach with process suspended"},
6786         {'h', "Show help dialog"},
6787         {'n', "Step over (source line)"},
6788         {'N', "Step over (single instruction)"},
6789         {'f', "Step out (finish)"},
6790         {'s', "Step in (source line)"},
6791         {'S', "Step in (single instruction)"},
6792         {'u', "Frame up"},
6793         {'d', "Frame down"},
6794         {',', "Page up"},
6795         {'.', "Page down"},
6796         {'\0', nullptr}};
6797     return g_source_view_key_help;
6798   }
6799 
WindowDelegateDraw(Window & window,bool force)6800   bool WindowDelegateDraw(Window &window, bool force) override {
6801     ExecutionContext exe_ctx =
6802         m_debugger.GetCommandInterpreter().GetExecutionContext();
6803     Process *process = exe_ctx.GetProcessPtr();
6804     Thread *thread = nullptr;
6805 
6806     bool update_location = false;
6807     if (process) {
6808       StateType state = process->GetState();
6809       if (StateIsStoppedState(state, true)) {
6810         // We are stopped, so it is ok to
6811         update_location = true;
6812       }
6813     }
6814 
6815     m_min_x = 1;
6816     m_min_y = 2;
6817     m_max_x = window.GetMaxX() - 1;
6818     m_max_y = window.GetMaxY() - 1;
6819 
6820     const uint32_t num_visible_lines = NumVisibleLines();
6821     StackFrameSP frame_sp;
6822     bool set_selected_line_to_pc = false;
6823 
6824     if (update_location) {
6825       const bool process_alive = process->IsAlive();
6826       bool thread_changed = false;
6827       if (process_alive) {
6828         thread = exe_ctx.GetThreadPtr();
6829         if (thread) {
6830           frame_sp = thread->GetSelectedFrame();
6831           auto tid = thread->GetID();
6832           thread_changed = tid != m_tid;
6833           m_tid = tid;
6834         } else {
6835           if (m_tid != LLDB_INVALID_THREAD_ID) {
6836             thread_changed = true;
6837             m_tid = LLDB_INVALID_THREAD_ID;
6838           }
6839         }
6840       }
6841       const uint32_t stop_id = process ? process->GetStopID() : 0;
6842       const bool stop_id_changed = stop_id != m_stop_id;
6843       bool frame_changed = false;
6844       m_stop_id = stop_id;
6845       m_title.Clear();
6846       if (frame_sp) {
6847         m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything);
6848         if (m_sc.module_sp) {
6849           m_title.Printf(
6850               "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString());
6851           ConstString func_name = m_sc.GetFunctionName();
6852           if (func_name)
6853             m_title.Printf("`%s", func_name.GetCString());
6854         }
6855         const uint32_t frame_idx = frame_sp->GetFrameIndex();
6856         frame_changed = frame_idx != m_frame_idx;
6857         m_frame_idx = frame_idx;
6858       } else {
6859         m_sc.Clear(true);
6860         frame_changed = m_frame_idx != UINT32_MAX;
6861         m_frame_idx = UINT32_MAX;
6862       }
6863 
6864       const bool context_changed =
6865           thread_changed || frame_changed || stop_id_changed;
6866 
6867       if (process_alive) {
6868         if (m_sc.line_entry.IsValid()) {
6869           m_pc_line = m_sc.line_entry.line;
6870           if (m_pc_line != UINT32_MAX)
6871             --m_pc_line; // Convert to zero based line number...
6872           // Update the selected line if the stop ID changed...
6873           if (context_changed)
6874             m_selected_line = m_pc_line;
6875 
6876           if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) {
6877             // Same file, nothing to do, we should either have the lines or
6878             // not (source file missing)
6879             if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) {
6880               if (m_selected_line >= m_first_visible_line + num_visible_lines)
6881                 m_first_visible_line = m_selected_line - 10;
6882             } else {
6883               if (m_selected_line > 10)
6884                 m_first_visible_line = m_selected_line - 10;
6885               else
6886                 m_first_visible_line = 0;
6887             }
6888           } else {
6889             // File changed, set selected line to the line with the PC
6890             m_selected_line = m_pc_line;
6891             m_file_sp =
6892                 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file);
6893             if (m_file_sp) {
6894               const size_t num_lines = m_file_sp->GetNumLines();
6895               m_line_width = 1;
6896               for (size_t n = num_lines; n >= 10; n = n / 10)
6897                 ++m_line_width;
6898 
6899               if (num_lines < num_visible_lines ||
6900                   m_selected_line < num_visible_lines)
6901                 m_first_visible_line = 0;
6902               else
6903                 m_first_visible_line = m_selected_line - 10;
6904             }
6905           }
6906         } else {
6907           m_file_sp.reset();
6908         }
6909 
6910         if (!m_file_sp || m_file_sp->GetNumLines() == 0) {
6911           // Show disassembly
6912           bool prefer_file_cache = false;
6913           if (m_sc.function) {
6914             if (m_disassembly_scope != m_sc.function) {
6915               m_disassembly_scope = m_sc.function;
6916               m_disassembly_sp = m_sc.function->GetInstructions(
6917                   exe_ctx, nullptr, !prefer_file_cache);
6918               if (m_disassembly_sp) {
6919                 set_selected_line_to_pc = true;
6920                 m_disassembly_range = m_sc.function->GetAddressRange();
6921               } else {
6922                 m_disassembly_range.Clear();
6923               }
6924             } else {
6925               set_selected_line_to_pc = context_changed;
6926             }
6927           } else if (m_sc.symbol) {
6928             if (m_disassembly_scope != m_sc.symbol) {
6929               m_disassembly_scope = m_sc.symbol;
6930               m_disassembly_sp = m_sc.symbol->GetInstructions(
6931                   exe_ctx, nullptr, prefer_file_cache);
6932               if (m_disassembly_sp) {
6933                 set_selected_line_to_pc = true;
6934                 m_disassembly_range.GetBaseAddress() =
6935                     m_sc.symbol->GetAddress();
6936                 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize());
6937               } else {
6938                 m_disassembly_range.Clear();
6939               }
6940             } else {
6941               set_selected_line_to_pc = context_changed;
6942             }
6943           }
6944         }
6945       } else {
6946         m_pc_line = UINT32_MAX;
6947       }
6948     }
6949 
6950     const int window_width = window.GetWidth();
6951     window.Erase();
6952     window.DrawTitleBox("Sources");
6953     if (!m_title.GetString().empty()) {
6954       window.AttributeOn(A_REVERSE);
6955       window.MoveCursor(1, 1);
6956       window.PutChar(' ');
6957       window.PutCStringTruncated(1, m_title.GetString().str().c_str());
6958       int x = window.GetCursorX();
6959       if (x < window_width - 1) {
6960         window.Printf("%*s", window_width - x - 1, "");
6961       }
6962       window.AttributeOff(A_REVERSE);
6963     }
6964 
6965     Target *target = exe_ctx.GetTargetPtr();
6966     const size_t num_source_lines = GetNumSourceLines();
6967     if (num_source_lines > 0) {
6968       // Display source
6969       BreakpointLines bp_lines;
6970       if (target) {
6971         BreakpointList &bp_list = target->GetBreakpointList();
6972         const size_t num_bps = bp_list.GetSize();
6973         for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
6974           BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
6975           const size_t num_bps_locs = bp_sp->GetNumLocations();
6976           for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
6977             BreakpointLocationSP bp_loc_sp =
6978                 bp_sp->GetLocationAtIndex(bp_loc_idx);
6979             LineEntry bp_loc_line_entry;
6980             if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
6981                     bp_loc_line_entry)) {
6982               if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) {
6983                 bp_lines.insert(bp_loc_line_entry.line);
6984               }
6985             }
6986           }
6987         }
6988       }
6989 
6990       for (size_t i = 0; i < num_visible_lines; ++i) {
6991         const uint32_t curr_line = m_first_visible_line + i;
6992         if (curr_line < num_source_lines) {
6993           const int line_y = m_min_y + i;
6994           window.MoveCursor(1, line_y);
6995           const bool is_pc_line = curr_line == m_pc_line;
6996           const bool line_is_selected = m_selected_line == curr_line;
6997           // Highlight the line as the PC line first (done by passing
6998           // argument to OutputColoredStringTruncated()), then if the selected
6999           // line isn't the same as the PC line, highlight it differently.
7000           attr_t highlight_attr = 0;
7001           attr_t bp_attr = 0;
7002           if (line_is_selected && !is_pc_line)
7003             highlight_attr = A_REVERSE;
7004 
7005           if (bp_lines.find(curr_line + 1) != bp_lines.end())
7006             bp_attr = COLOR_PAIR(BlackOnWhite);
7007 
7008           if (bp_attr)
7009             window.AttributeOn(bp_attr);
7010 
7011           window.Printf(" %*u ", m_line_width, curr_line + 1);
7012 
7013           if (bp_attr)
7014             window.AttributeOff(bp_attr);
7015 
7016           window.PutChar(ACS_VLINE);
7017           // Mark the line with the PC with a diamond
7018           if (is_pc_line)
7019             window.PutChar(ACS_DIAMOND);
7020           else
7021             window.PutChar(' ');
7022 
7023           if (highlight_attr)
7024             window.AttributeOn(highlight_attr);
7025 
7026           StreamString lineStream;
7027 
7028           std::optional<size_t> column;
7029           if (is_pc_line && m_sc.line_entry.IsValid() && m_sc.line_entry.column)
7030             column = m_sc.line_entry.column - 1;
7031           m_file_sp->DisplaySourceLines(curr_line + 1, column, 0, 0,
7032                                         &lineStream);
7033           StringRef line = lineStream.GetString();
7034           if (line.endswith("\n"))
7035             line = line.drop_back();
7036           bool wasWritten = window.OutputColoredStringTruncated(
7037               1, line, m_first_visible_column, is_pc_line);
7038           if (!wasWritten && (line_is_selected || is_pc_line)) {
7039             // Draw an empty space to show the selected/PC line if empty,
7040             // or draw '<' if nothing is visible because of scrolling too much
7041             // to the right.
7042             window.PutCStringTruncated(
7043                 1, line.empty() && m_first_visible_column == 0 ? " " : "<");
7044           }
7045 
7046           if (is_pc_line && frame_sp &&
7047               frame_sp->GetConcreteFrameIndex() == 0) {
7048             StopInfoSP stop_info_sp;
7049             if (thread)
7050               stop_info_sp = thread->GetStopInfo();
7051             if (stop_info_sp) {
7052               const char *stop_description = stop_info_sp->GetDescription();
7053               if (stop_description && stop_description[0]) {
7054                 size_t stop_description_len = strlen(stop_description);
7055                 int desc_x = window_width - stop_description_len - 16;
7056                 if (desc_x - window.GetCursorX() > 0)
7057                   window.Printf("%*s", desc_x - window.GetCursorX(), "");
7058                 window.MoveCursor(window_width - stop_description_len - 16,
7059                                   line_y);
7060                 const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue);
7061                 window.AttributeOn(stop_reason_attr);
7062                 window.PrintfTruncated(1, " <<< Thread %u: %s ",
7063                                        thread->GetIndexID(), stop_description);
7064                 window.AttributeOff(stop_reason_attr);
7065               }
7066             } else {
7067               window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
7068             }
7069           }
7070           if (highlight_attr)
7071             window.AttributeOff(highlight_attr);
7072         } else {
7073           break;
7074         }
7075       }
7076     } else {
7077       size_t num_disassembly_lines = GetNumDisassemblyLines();
7078       if (num_disassembly_lines > 0) {
7079         // Display disassembly
7080         BreakpointAddrs bp_file_addrs;
7081         Target *target = exe_ctx.GetTargetPtr();
7082         if (target) {
7083           BreakpointList &bp_list = target->GetBreakpointList();
7084           const size_t num_bps = bp_list.GetSize();
7085           for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7086             BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7087             const size_t num_bps_locs = bp_sp->GetNumLocations();
7088             for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs;
7089                  ++bp_loc_idx) {
7090               BreakpointLocationSP bp_loc_sp =
7091                   bp_sp->GetLocationAtIndex(bp_loc_idx);
7092               LineEntry bp_loc_line_entry;
7093               const lldb::addr_t file_addr =
7094                   bp_loc_sp->GetAddress().GetFileAddress();
7095               if (file_addr != LLDB_INVALID_ADDRESS) {
7096                 if (m_disassembly_range.ContainsFileAddress(file_addr))
7097                   bp_file_addrs.insert(file_addr);
7098               }
7099             }
7100           }
7101         }
7102 
7103         const attr_t selected_highlight_attr = A_REVERSE;
7104         const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue);
7105 
7106         StreamString strm;
7107 
7108         InstructionList &insts = m_disassembly_sp->GetInstructionList();
7109         Address pc_address;
7110 
7111         if (frame_sp)
7112           pc_address = frame_sp->GetFrameCodeAddress();
7113         const uint32_t pc_idx =
7114             pc_address.IsValid()
7115                 ? insts.GetIndexOfInstructionAtAddress(pc_address)
7116                 : UINT32_MAX;
7117         if (set_selected_line_to_pc) {
7118           m_selected_line = pc_idx;
7119         }
7120 
7121         const uint32_t non_visible_pc_offset = (num_visible_lines / 5);
7122         if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines)
7123           m_first_visible_line = 0;
7124 
7125         if (pc_idx < num_disassembly_lines) {
7126           if (pc_idx < static_cast<uint32_t>(m_first_visible_line) ||
7127               pc_idx >= m_first_visible_line + num_visible_lines)
7128             m_first_visible_line = pc_idx - non_visible_pc_offset;
7129         }
7130 
7131         for (size_t i = 0; i < num_visible_lines; ++i) {
7132           const uint32_t inst_idx = m_first_visible_line + i;
7133           Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get();
7134           if (!inst)
7135             break;
7136 
7137           const int line_y = m_min_y + i;
7138           window.MoveCursor(1, line_y);
7139           const bool is_pc_line = frame_sp && inst_idx == pc_idx;
7140           const bool line_is_selected = m_selected_line == inst_idx;
7141           // Highlight the line as the PC line first, then if the selected
7142           // line isn't the same as the PC line, highlight it differently
7143           attr_t highlight_attr = 0;
7144           attr_t bp_attr = 0;
7145           if (is_pc_line)
7146             highlight_attr = pc_highlight_attr;
7147           else if (line_is_selected)
7148             highlight_attr = selected_highlight_attr;
7149 
7150           if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) !=
7151               bp_file_addrs.end())
7152             bp_attr = COLOR_PAIR(BlackOnWhite);
7153 
7154           if (bp_attr)
7155             window.AttributeOn(bp_attr);
7156 
7157           window.Printf(" 0x%16.16llx ",
7158                         static_cast<unsigned long long>(
7159                             inst->GetAddress().GetLoadAddress(target)));
7160 
7161           if (bp_attr)
7162             window.AttributeOff(bp_attr);
7163 
7164           window.PutChar(ACS_VLINE);
7165           // Mark the line with the PC with a diamond
7166           if (is_pc_line)
7167             window.PutChar(ACS_DIAMOND);
7168           else
7169             window.PutChar(' ');
7170 
7171           if (highlight_attr)
7172             window.AttributeOn(highlight_attr);
7173 
7174           const char *mnemonic = inst->GetMnemonic(&exe_ctx);
7175           const char *operands = inst->GetOperands(&exe_ctx);
7176           const char *comment = inst->GetComment(&exe_ctx);
7177 
7178           if (mnemonic != nullptr && mnemonic[0] == '\0')
7179             mnemonic = nullptr;
7180           if (operands != nullptr && operands[0] == '\0')
7181             operands = nullptr;
7182           if (comment != nullptr && comment[0] == '\0')
7183             comment = nullptr;
7184 
7185           strm.Clear();
7186 
7187           if (mnemonic != nullptr && operands != nullptr && comment != nullptr)
7188             strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment);
7189           else if (mnemonic != nullptr && operands != nullptr)
7190             strm.Printf("%-8s %s", mnemonic, operands);
7191           else if (mnemonic != nullptr)
7192             strm.Printf("%s", mnemonic);
7193 
7194           int right_pad = 1;
7195           window.PutCStringTruncated(
7196               right_pad,
7197               strm.GetString().substr(m_first_visible_column).data());
7198 
7199           if (is_pc_line && frame_sp &&
7200               frame_sp->GetConcreteFrameIndex() == 0) {
7201             StopInfoSP stop_info_sp;
7202             if (thread)
7203               stop_info_sp = thread->GetStopInfo();
7204             if (stop_info_sp) {
7205               const char *stop_description = stop_info_sp->GetDescription();
7206               if (stop_description && stop_description[0]) {
7207                 size_t stop_description_len = strlen(stop_description);
7208                 int desc_x = window_width - stop_description_len - 16;
7209                 if (desc_x - window.GetCursorX() > 0)
7210                   window.Printf("%*s", desc_x - window.GetCursorX(), "");
7211                 window.MoveCursor(window_width - stop_description_len - 15,
7212                                   line_y);
7213                 if (thread)
7214                   window.PrintfTruncated(1, "<<< Thread %u: %s ",
7215                                          thread->GetIndexID(),
7216                                          stop_description);
7217               }
7218             } else {
7219               window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
7220             }
7221           }
7222           if (highlight_attr)
7223             window.AttributeOff(highlight_attr);
7224         }
7225       }
7226     }
7227     return true; // Drawing handled
7228   }
7229 
GetNumLines()7230   size_t GetNumLines() {
7231     size_t num_lines = GetNumSourceLines();
7232     if (num_lines == 0)
7233       num_lines = GetNumDisassemblyLines();
7234     return num_lines;
7235   }
7236 
GetNumSourceLines() const7237   size_t GetNumSourceLines() const {
7238     if (m_file_sp)
7239       return m_file_sp->GetNumLines();
7240     return 0;
7241   }
7242 
GetNumDisassemblyLines() const7243   size_t GetNumDisassemblyLines() const {
7244     if (m_disassembly_sp)
7245       return m_disassembly_sp->GetInstructionList().GetSize();
7246     return 0;
7247   }
7248 
WindowDelegateHandleChar(Window & window,int c)7249   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
7250     const uint32_t num_visible_lines = NumVisibleLines();
7251     const size_t num_lines = GetNumLines();
7252 
7253     switch (c) {
7254     case ',':
7255     case KEY_PPAGE:
7256       // Page up key
7257       if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines)
7258         m_first_visible_line -= num_visible_lines;
7259       else
7260         m_first_visible_line = 0;
7261       m_selected_line = m_first_visible_line;
7262       return eKeyHandled;
7263 
7264     case '.':
7265     case KEY_NPAGE:
7266       // Page down key
7267       {
7268         if (m_first_visible_line + num_visible_lines < num_lines)
7269           m_first_visible_line += num_visible_lines;
7270         else if (num_lines < num_visible_lines)
7271           m_first_visible_line = 0;
7272         else
7273           m_first_visible_line = num_lines - num_visible_lines;
7274         m_selected_line = m_first_visible_line;
7275       }
7276       return eKeyHandled;
7277 
7278     case KEY_UP:
7279       if (m_selected_line > 0) {
7280         m_selected_line--;
7281         if (static_cast<size_t>(m_first_visible_line) > m_selected_line)
7282           m_first_visible_line = m_selected_line;
7283       }
7284       return eKeyHandled;
7285 
7286     case KEY_DOWN:
7287       if (m_selected_line + 1 < num_lines) {
7288         m_selected_line++;
7289         if (m_first_visible_line + num_visible_lines < m_selected_line)
7290           m_first_visible_line++;
7291       }
7292       return eKeyHandled;
7293 
7294     case KEY_LEFT:
7295       if (m_first_visible_column > 0)
7296         --m_first_visible_column;
7297       return eKeyHandled;
7298 
7299     case KEY_RIGHT:
7300       ++m_first_visible_column;
7301       return eKeyHandled;
7302 
7303     case '\r':
7304     case '\n':
7305     case KEY_ENTER:
7306       // Set a breakpoint and run to the line using a one shot breakpoint
7307       if (GetNumSourceLines() > 0) {
7308         ExecutionContext exe_ctx =
7309             m_debugger.GetCommandInterpreter().GetExecutionContext();
7310         if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) {
7311           BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
7312               nullptr, // Don't limit the breakpoint to certain modules
7313               m_file_sp->GetFileSpec(), // Source file
7314               m_selected_line +
7315                   1, // Source line number (m_selected_line is zero based)
7316               0,     // Unspecified column.
7317               0,     // No offset
7318               eLazyBoolCalculate,  // Check inlines using global setting
7319               eLazyBoolCalculate,  // Skip prologue using global setting,
7320               false,               // internal
7321               false,               // request_hardware
7322               eLazyBoolCalculate); // move_to_nearest_code
7323           // Make breakpoint one shot
7324           bp_sp->GetOptions().SetOneShot(true);
7325           exe_ctx.GetProcessRef().Resume();
7326         }
7327       } else if (m_selected_line < GetNumDisassemblyLines()) {
7328         const Instruction *inst = m_disassembly_sp->GetInstructionList()
7329                                       .GetInstructionAtIndex(m_selected_line)
7330                                       .get();
7331         ExecutionContext exe_ctx =
7332             m_debugger.GetCommandInterpreter().GetExecutionContext();
7333         if (exe_ctx.HasTargetScope()) {
7334           Address addr = inst->GetAddress();
7335           BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
7336               addr,   // lldb_private::Address
7337               false,  // internal
7338               false); // request_hardware
7339           // Make breakpoint one shot
7340           bp_sp->GetOptions().SetOneShot(true);
7341           exe_ctx.GetProcessRef().Resume();
7342         }
7343       }
7344       return eKeyHandled;
7345 
7346     case 'b': // 'b' == toggle breakpoint on currently selected line
7347       ToggleBreakpointOnSelectedLine();
7348       return eKeyHandled;
7349 
7350     case 'D': // 'D' == detach and keep stopped
7351     {
7352       ExecutionContext exe_ctx =
7353           m_debugger.GetCommandInterpreter().GetExecutionContext();
7354       if (exe_ctx.HasProcessScope())
7355         exe_ctx.GetProcessRef().Detach(true);
7356     }
7357       return eKeyHandled;
7358 
7359     case 'c':
7360       // 'c' == continue
7361       {
7362         ExecutionContext exe_ctx =
7363             m_debugger.GetCommandInterpreter().GetExecutionContext();
7364         if (exe_ctx.HasProcessScope())
7365           exe_ctx.GetProcessRef().Resume();
7366       }
7367       return eKeyHandled;
7368 
7369     case 'f':
7370       // 'f' == step out (finish)
7371       {
7372         ExecutionContext exe_ctx =
7373             m_debugger.GetCommandInterpreter().GetExecutionContext();
7374         if (exe_ctx.HasThreadScope() &&
7375             StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
7376           Thread *thread = exe_ctx.GetThreadPtr();
7377           uint32_t frame_idx = thread->GetSelectedFrameIndex();
7378           exe_ctx.GetThreadRef().StepOut(frame_idx);
7379         }
7380       }
7381       return eKeyHandled;
7382 
7383     case 'n': // 'n' == step over
7384     case 'N': // 'N' == step over instruction
7385     {
7386       ExecutionContext exe_ctx =
7387           m_debugger.GetCommandInterpreter().GetExecutionContext();
7388       if (exe_ctx.HasThreadScope() &&
7389           StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
7390         bool source_step = (c == 'n');
7391         exe_ctx.GetThreadRef().StepOver(source_step);
7392       }
7393     }
7394       return eKeyHandled;
7395 
7396     case 's': // 's' == step into
7397     case 'S': // 'S' == step into instruction
7398     {
7399       ExecutionContext exe_ctx =
7400           m_debugger.GetCommandInterpreter().GetExecutionContext();
7401       if (exe_ctx.HasThreadScope() &&
7402           StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
7403         bool source_step = (c == 's');
7404         exe_ctx.GetThreadRef().StepIn(source_step);
7405       }
7406     }
7407       return eKeyHandled;
7408 
7409     case 'u': // 'u' == frame up
7410     case 'd': // 'd' == frame down
7411     {
7412       ExecutionContext exe_ctx =
7413           m_debugger.GetCommandInterpreter().GetExecutionContext();
7414       if (exe_ctx.HasThreadScope()) {
7415         Thread *thread = exe_ctx.GetThreadPtr();
7416         uint32_t frame_idx = thread->GetSelectedFrameIndex();
7417         if (frame_idx == UINT32_MAX)
7418           frame_idx = 0;
7419         if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount())
7420           ++frame_idx;
7421         else if (c == 'd' && frame_idx > 0)
7422           --frame_idx;
7423         if (thread->SetSelectedFrameByIndex(frame_idx, true))
7424           exe_ctx.SetFrameSP(thread->GetSelectedFrame());
7425       }
7426     }
7427       return eKeyHandled;
7428 
7429     case 'h':
7430       window.CreateHelpSubwindow();
7431       return eKeyHandled;
7432 
7433     default:
7434       break;
7435     }
7436     return eKeyNotHandled;
7437   }
7438 
ToggleBreakpointOnSelectedLine()7439   void ToggleBreakpointOnSelectedLine() {
7440     ExecutionContext exe_ctx =
7441         m_debugger.GetCommandInterpreter().GetExecutionContext();
7442     if (!exe_ctx.HasTargetScope())
7443       return;
7444     if (GetNumSourceLines() > 0) {
7445       // Source file breakpoint.
7446       BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
7447       const size_t num_bps = bp_list.GetSize();
7448       for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7449         BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7450         const size_t num_bps_locs = bp_sp->GetNumLocations();
7451         for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
7452           BreakpointLocationSP bp_loc_sp =
7453               bp_sp->GetLocationAtIndex(bp_loc_idx);
7454           LineEntry bp_loc_line_entry;
7455           if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
7456                   bp_loc_line_entry)) {
7457             if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file &&
7458                 m_selected_line + 1 == bp_loc_line_entry.line) {
7459               bool removed =
7460                   exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
7461               assert(removed);
7462               UNUSED_IF_ASSERT_DISABLED(removed);
7463               return; // Existing breakpoint removed.
7464             }
7465           }
7466         }
7467       }
7468       // No breakpoint found on the location, add it.
7469       BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
7470           nullptr, // Don't limit the breakpoint to certain modules
7471           m_file_sp->GetFileSpec(), // Source file
7472           m_selected_line +
7473               1, // Source line number (m_selected_line is zero based)
7474           0,     // No column specified.
7475           0,     // No offset
7476           eLazyBoolCalculate,  // Check inlines using global setting
7477           eLazyBoolCalculate,  // Skip prologue using global setting,
7478           false,               // internal
7479           false,               // request_hardware
7480           eLazyBoolCalculate); // move_to_nearest_code
7481     } else {
7482       // Disassembly breakpoint.
7483       assert(GetNumDisassemblyLines() > 0);
7484       assert(m_selected_line < GetNumDisassemblyLines());
7485       const Instruction *inst = m_disassembly_sp->GetInstructionList()
7486                                     .GetInstructionAtIndex(m_selected_line)
7487                                     .get();
7488       Address addr = inst->GetAddress();
7489       // Try to find it.
7490       BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
7491       const size_t num_bps = bp_list.GetSize();
7492       for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7493         BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7494         const size_t num_bps_locs = bp_sp->GetNumLocations();
7495         for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
7496           BreakpointLocationSP bp_loc_sp =
7497               bp_sp->GetLocationAtIndex(bp_loc_idx);
7498           LineEntry bp_loc_line_entry;
7499           const lldb::addr_t file_addr =
7500               bp_loc_sp->GetAddress().GetFileAddress();
7501           if (file_addr == addr.GetFileAddress()) {
7502             bool removed =
7503                 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
7504             assert(removed);
7505             UNUSED_IF_ASSERT_DISABLED(removed);
7506             return; // Existing breakpoint removed.
7507           }
7508         }
7509       }
7510       // No breakpoint found on the address, add it.
7511       BreakpointSP bp_sp =
7512           exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address
7513                                                   false,  // internal
7514                                                   false); // request_hardware
7515     }
7516   }
7517 
7518 protected:
7519   typedef std::set<uint32_t> BreakpointLines;
7520   typedef std::set<lldb::addr_t> BreakpointAddrs;
7521 
7522   Debugger &m_debugger;
7523   SymbolContext m_sc;
7524   SourceManager::FileSP m_file_sp;
7525   SymbolContextScope *m_disassembly_scope = nullptr;
7526   lldb::DisassemblerSP m_disassembly_sp;
7527   AddressRange m_disassembly_range;
7528   StreamString m_title;
7529   lldb::user_id_t m_tid = LLDB_INVALID_THREAD_ID;
7530   int m_line_width = 4;
7531   uint32_t m_selected_line = 0; // The selected line
7532   uint32_t m_pc_line = 0;       // The line with the PC
7533   uint32_t m_stop_id = 0;
7534   uint32_t m_frame_idx = UINT32_MAX;
7535   int m_first_visible_line = 0;
7536   int m_first_visible_column = 0;
7537   int m_min_x = 0;
7538   int m_min_y = 0;
7539   int m_max_x = 0;
7540   int m_max_y = 0;
7541 };
7542 
7543 DisplayOptions ValueObjectListDelegate::g_options = {true};
7544 
IOHandlerCursesGUI(Debugger & debugger)7545 IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger)
7546     : IOHandler(debugger, IOHandler::Type::Curses) {}
7547 
Activate()7548 void IOHandlerCursesGUI::Activate() {
7549   IOHandler::Activate();
7550   if (!m_app_ap) {
7551     m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE());
7552 
7553     // This is both a window and a menu delegate
7554     std::shared_ptr<ApplicationDelegate> app_delegate_sp(
7555         new ApplicationDelegate(*m_app_ap, m_debugger));
7556 
7557     MenuDelegateSP app_menu_delegate_sp =
7558         std::static_pointer_cast<MenuDelegate>(app_delegate_sp);
7559     MenuSP lldb_menu_sp(
7560         new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB));
7561     MenuSP exit_menuitem_sp(
7562         new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit));
7563     exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit);
7564     lldb_menu_sp->AddSubmenu(MenuSP(new Menu(
7565         "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout)));
7566     lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
7567     lldb_menu_sp->AddSubmenu(exit_menuitem_sp);
7568 
7569     MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2),
7570                                    ApplicationDelegate::eMenuID_Target));
7571     target_menu_sp->AddSubmenu(MenuSP(new Menu(
7572         "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate)));
7573     target_menu_sp->AddSubmenu(MenuSP(new Menu(
7574         "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete)));
7575 
7576     MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3),
7577                                     ApplicationDelegate::eMenuID_Process));
7578     process_menu_sp->AddSubmenu(MenuSP(new Menu(
7579         "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach)));
7580     process_menu_sp->AddSubmenu(
7581         MenuSP(new Menu("Detach and resume", nullptr, 'd',
7582                         ApplicationDelegate::eMenuID_ProcessDetachResume)));
7583     process_menu_sp->AddSubmenu(
7584         MenuSP(new Menu("Detach suspended", nullptr, 's',
7585                         ApplicationDelegate::eMenuID_ProcessDetachSuspended)));
7586     process_menu_sp->AddSubmenu(MenuSP(new Menu(
7587         "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch)));
7588     process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
7589     process_menu_sp->AddSubmenu(
7590         MenuSP(new Menu("Continue", nullptr, 'c',
7591                         ApplicationDelegate::eMenuID_ProcessContinue)));
7592     process_menu_sp->AddSubmenu(MenuSP(new Menu(
7593         "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt)));
7594     process_menu_sp->AddSubmenu(MenuSP(new Menu(
7595         "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill)));
7596 
7597     MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4),
7598                                    ApplicationDelegate::eMenuID_Thread));
7599     thread_menu_sp->AddSubmenu(MenuSP(new Menu(
7600         "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn)));
7601     thread_menu_sp->AddSubmenu(
7602         MenuSP(new Menu("Step Over", nullptr, 'v',
7603                         ApplicationDelegate::eMenuID_ThreadStepOver)));
7604     thread_menu_sp->AddSubmenu(MenuSP(new Menu(
7605         "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut)));
7606 
7607     MenuSP view_menu_sp(
7608         new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View));
7609     view_menu_sp->AddSubmenu(
7610         MenuSP(new Menu("Backtrace", nullptr, 't',
7611                         ApplicationDelegate::eMenuID_ViewBacktrace)));
7612     view_menu_sp->AddSubmenu(
7613         MenuSP(new Menu("Registers", nullptr, 'r',
7614                         ApplicationDelegate::eMenuID_ViewRegisters)));
7615     view_menu_sp->AddSubmenu(MenuSP(new Menu(
7616         "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource)));
7617     view_menu_sp->AddSubmenu(
7618         MenuSP(new Menu("Variables", nullptr, 'v',
7619                         ApplicationDelegate::eMenuID_ViewVariables)));
7620     view_menu_sp->AddSubmenu(
7621         MenuSP(new Menu("Breakpoints", nullptr, 'b',
7622                         ApplicationDelegate::eMenuID_ViewBreakpoints)));
7623 
7624     MenuSP help_menu_sp(
7625         new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help));
7626     help_menu_sp->AddSubmenu(MenuSP(new Menu(
7627         "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp)));
7628 
7629     m_app_ap->Initialize();
7630     WindowSP &main_window_sp = m_app_ap->GetMainWindow();
7631 
7632     MenuSP menubar_sp(new Menu(Menu::Type::Bar));
7633     menubar_sp->AddSubmenu(lldb_menu_sp);
7634     menubar_sp->AddSubmenu(target_menu_sp);
7635     menubar_sp->AddSubmenu(process_menu_sp);
7636     menubar_sp->AddSubmenu(thread_menu_sp);
7637     menubar_sp->AddSubmenu(view_menu_sp);
7638     menubar_sp->AddSubmenu(help_menu_sp);
7639     menubar_sp->SetDelegate(app_menu_delegate_sp);
7640 
7641     Rect content_bounds = main_window_sp->GetFrame();
7642     Rect menubar_bounds = content_bounds.MakeMenuBar();
7643     Rect status_bounds = content_bounds.MakeStatusBar();
7644     Rect source_bounds;
7645     Rect variables_bounds;
7646     Rect threads_bounds;
7647     Rect source_variables_bounds;
7648     content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
7649                                            threads_bounds);
7650     source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds,
7651                                                       variables_bounds);
7652 
7653     WindowSP menubar_window_sp =
7654         main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false);
7655     // Let the menubar get keys if the active window doesn't handle the keys
7656     // that are typed so it can respond to menubar key presses.
7657     menubar_window_sp->SetCanBeActive(
7658         false); // Don't let the menubar become the active window
7659     menubar_window_sp->SetDelegate(menubar_sp);
7660 
7661     WindowSP source_window_sp(
7662         main_window_sp->CreateSubWindow("Source", source_bounds, true));
7663     WindowSP variables_window_sp(
7664         main_window_sp->CreateSubWindow("Variables", variables_bounds, false));
7665     WindowSP threads_window_sp(
7666         main_window_sp->CreateSubWindow("Threads", threads_bounds, false));
7667     WindowSP status_window_sp(
7668         main_window_sp->CreateSubWindow("Status", status_bounds, false));
7669     status_window_sp->SetCanBeActive(
7670         false); // Don't let the status bar become the active window
7671     main_window_sp->SetDelegate(
7672         std::static_pointer_cast<WindowDelegate>(app_delegate_sp));
7673     source_window_sp->SetDelegate(
7674         WindowDelegateSP(new SourceFileWindowDelegate(m_debugger)));
7675     variables_window_sp->SetDelegate(
7676         WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
7677     TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger));
7678     threads_window_sp->SetDelegate(WindowDelegateSP(
7679         new TreeWindowDelegate(m_debugger, thread_delegate_sp)));
7680     status_window_sp->SetDelegate(
7681         WindowDelegateSP(new StatusBarWindowDelegate(m_debugger)));
7682 
7683     // All colors with black background.
7684     init_pair(1, COLOR_BLACK, COLOR_BLACK);
7685     init_pair(2, COLOR_RED, COLOR_BLACK);
7686     init_pair(3, COLOR_GREEN, COLOR_BLACK);
7687     init_pair(4, COLOR_YELLOW, COLOR_BLACK);
7688     init_pair(5, COLOR_BLUE, COLOR_BLACK);
7689     init_pair(6, COLOR_MAGENTA, COLOR_BLACK);
7690     init_pair(7, COLOR_CYAN, COLOR_BLACK);
7691     init_pair(8, COLOR_WHITE, COLOR_BLACK);
7692     // All colors with blue background.
7693     init_pair(9, COLOR_BLACK, COLOR_BLUE);
7694     init_pair(10, COLOR_RED, COLOR_BLUE);
7695     init_pair(11, COLOR_GREEN, COLOR_BLUE);
7696     init_pair(12, COLOR_YELLOW, COLOR_BLUE);
7697     init_pair(13, COLOR_BLUE, COLOR_BLUE);
7698     init_pair(14, COLOR_MAGENTA, COLOR_BLUE);
7699     init_pair(15, COLOR_CYAN, COLOR_BLUE);
7700     init_pair(16, COLOR_WHITE, COLOR_BLUE);
7701     // These must match the order in the color indexes enum.
7702     init_pair(17, COLOR_BLACK, COLOR_WHITE);
7703     init_pair(18, COLOR_MAGENTA, COLOR_WHITE);
7704     static_assert(LastColorPairIndex == 18, "Color indexes do not match.");
7705 
7706     define_key("\033[Z", KEY_SHIFT_TAB);
7707     define_key("\033\015", KEY_ALT_ENTER);
7708   }
7709 }
7710 
Deactivate()7711 void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); }
7712 
Run()7713 void IOHandlerCursesGUI::Run() {
7714   m_app_ap->Run(m_debugger);
7715   SetIsDone(true);
7716 }
7717 
7718 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default;
7719 
Cancel()7720 void IOHandlerCursesGUI::Cancel() {}
7721 
Interrupt()7722 bool IOHandlerCursesGUI::Interrupt() {
7723   return m_debugger.GetCommandInterpreter().IOHandlerInterrupt(*this);
7724 }
7725 
GotEOF()7726 void IOHandlerCursesGUI::GotEOF() {}
7727 
TerminalSizeChanged()7728 void IOHandlerCursesGUI::TerminalSizeChanged() {
7729   m_app_ap->TerminalSizeChanged();
7730 }
7731 
7732 #endif // LLDB_ENABLE_CURSES
7733