1 //===-- IOHandlerCursesGUI.cpp --------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "lldb/Core/IOHandlerCursesGUI.h"
10 #include "lldb/Host/Config.h"
11 
12 #if LLDB_ENABLE_CURSES
13 #if CURSES_HAVE_NCURSES_CURSES_H
14 #include <ncurses/curses.h>
15 #include <ncurses/panel.h>
16 #else
17 #include <curses.h>
18 #include <panel.h>
19 #endif
20 #endif
21 
22 #if defined(__APPLE__)
23 #include <deque>
24 #endif
25 #include <string>
26 
27 #include "lldb/Core/Debugger.h"
28 #include "lldb/Core/StreamFile.h"
29 #include "lldb/Core/ValueObjectUpdater.h"
30 #include "lldb/Host/File.h"
31 #include "lldb/Utility/Predicate.h"
32 #include "lldb/Utility/Status.h"
33 #include "lldb/Utility/StreamString.h"
34 #include "lldb/Utility/StringList.h"
35 #include "lldb/lldb-forward.h"
36 
37 #include "lldb/Interpreter/CommandCompletions.h"
38 #include "lldb/Interpreter/CommandInterpreter.h"
39 #include "lldb/Interpreter/OptionGroupPlatform.h"
40 
41 #if LLDB_ENABLE_CURSES
42 #include "lldb/Breakpoint/BreakpointLocation.h"
43 #include "lldb/Core/Module.h"
44 #include "lldb/Core/PluginManager.h"
45 #include "lldb/Core/ValueObject.h"
46 #include "lldb/Core/ValueObjectRegister.h"
47 #include "lldb/Symbol/Block.h"
48 #include "lldb/Symbol/CompileUnit.h"
49 #include "lldb/Symbol/Function.h"
50 #include "lldb/Symbol/Symbol.h"
51 #include "lldb/Symbol/VariableList.h"
52 #include "lldb/Target/Process.h"
53 #include "lldb/Target/RegisterContext.h"
54 #include "lldb/Target/StackFrame.h"
55 #include "lldb/Target/StopInfo.h"
56 #include "lldb/Target/Target.h"
57 #include "lldb/Target/Thread.h"
58 #include "lldb/Utility/State.h"
59 #endif
60 
61 #include "llvm/ADT/StringRef.h"
62 
63 #ifdef _WIN32
64 #include "lldb/Host/windows/windows.h"
65 #endif
66 
67 #include <memory>
68 #include <mutex>
69 
70 #include <cassert>
71 #include <cctype>
72 #include <cerrno>
73 #include <cstdint>
74 #include <cstdio>
75 #include <cstring>
76 #include <functional>
77 #include <type_traits>
78 
79 using namespace lldb;
80 using namespace lldb_private;
81 using llvm::None;
82 using llvm::Optional;
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;
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), m_window(nullptr) {}
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('\x1b');
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("\x1b");
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       if (!string.consume_front("[")) {
529         llvm::errs() << "Missing '[' in color escape sequence.\n";
530         continue;
531       }
532       // Only 8 basic foreground colors and reset, our Highlighter doesn't use
533       // anything else.
534       int value;
535       if (!!string.consumeInteger(10, value) || // Returns false on success.
536           !(value == 0 || (value >= 30 && value <= 37))) {
537         llvm::errs() << "No valid color code in color escape sequence.\n";
538         continue;
539       }
540       if (!string.consume_front("m")) {
541         llvm::errs() << "Missing 'm' in color escape sequence.\n";
542         continue;
543       }
544       if (value == 0) { // Reset.
545         wattr_set(m_window, saved_attr, saved_pair, nullptr);
546         if (use_blue_background)
547           ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
548       } else {
549         // Mapped directly to first 16 color pairs (black/blue background).
550         ::wattron(m_window,
551                   COLOR_PAIR(value - 30 + 1 + (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;
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_parent(nullptr),
593         m_subwindows(), m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX),
594         m_prev_active_window_idx(UINT32_MAX), m_delete(true),
595         m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
596     Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y,
597                    bounds.origin.y));
598   }
599 
~Window()600   virtual ~Window() {
601     RemoveSubWindows();
602     Reset();
603   }
604 
Reset(WINDOW * w=nullptr,bool del=true)605   void Reset(WINDOW *w = nullptr, bool del = true) {
606     if (m_window == w)
607       return;
608 
609     if (m_panel) {
610       ::del_panel(m_panel);
611       m_panel = nullptr;
612     }
613     if (m_window && m_delete) {
614       ::delwin(m_window);
615       m_window = nullptr;
616       m_delete = false;
617     }
618     if (w) {
619       m_window = w;
620       m_panel = ::new_panel(m_window);
621       m_delete = del;
622     }
623   }
624 
625   // Get the rectangle in our parent window
GetBounds() const626   Rect GetBounds() const { return Rect(GetParentOrigin(), GetSize()); }
627 
GetCenteredRect(int width,int height)628   Rect GetCenteredRect(int width, int height) {
629     Size size = GetSize();
630     width = std::min(size.width, width);
631     height = std::min(size.height, height);
632     int x = (size.width - width) / 2;
633     int y = (size.height - height) / 2;
634     return Rect(Point(x, y), Size(width, height));
635   }
636 
GetChar()637   int GetChar() { return ::wgetch(m_window); }
GetParentOrigin() const638   Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); }
GetParentX() const639   int GetParentX() const { return getparx(m_window); }
GetParentY() const640   int GetParentY() const { return getpary(m_window); }
MoveWindow(int x,int y)641   void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); }
Resize(int w,int h)642   void Resize(int w, int h) { ::wresize(m_window, h, w); }
Resize(const Size & size)643   void Resize(const Size &size) {
644     ::wresize(m_window, size.height, size.width);
645   }
MoveWindow(const Point & origin)646   void MoveWindow(const Point &origin) {
647     const bool moving_window = origin != GetParentOrigin();
648     if (m_is_subwin && moving_window) {
649       // Can't move subwindows, must delete and re-create
650       Size size = GetSize();
651       Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y,
652                      origin.x),
653             true);
654     } else {
655       ::mvwin(m_window, origin.y, origin.x);
656     }
657   }
658 
SetBounds(const Rect & bounds)659   void SetBounds(const Rect &bounds) {
660     const bool moving_window = bounds.origin != GetParentOrigin();
661     if (m_is_subwin && moving_window) {
662       // Can't move subwindows, must delete and re-create
663       Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width,
664                      bounds.origin.y, bounds.origin.x),
665             true);
666     } else {
667       if (moving_window)
668         MoveWindow(bounds.origin);
669       Resize(bounds.size);
670     }
671   }
672 
Touch()673   void Touch() {
674     ::touchwin(m_window);
675     if (m_parent)
676       m_parent->Touch();
677   }
678 
CreateSubWindow(const char * name,const Rect & bounds,bool make_active)679   WindowSP CreateSubWindow(const char *name, const Rect &bounds,
680                            bool make_active) {
681     auto get_window = [this, &bounds]() {
682       return m_window
683                  ? ::subwin(m_window, bounds.size.height, bounds.size.width,
684                             bounds.origin.y, bounds.origin.x)
685                  : ::newwin(bounds.size.height, bounds.size.width,
686                             bounds.origin.y, bounds.origin.x);
687     };
688     WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true);
689     subwindow_sp->m_is_subwin = subwindow_sp.operator bool();
690     subwindow_sp->m_parent = this;
691     if (make_active) {
692       m_prev_active_window_idx = m_curr_active_window_idx;
693       m_curr_active_window_idx = m_subwindows.size();
694     }
695     m_subwindows.push_back(subwindow_sp);
696     ::top_panel(subwindow_sp->m_panel);
697     m_needs_update = true;
698     return subwindow_sp;
699   }
700 
RemoveSubWindow(Window * window)701   bool RemoveSubWindow(Window *window) {
702     Windows::iterator pos, end = m_subwindows.end();
703     size_t i = 0;
704     for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
705       if ((*pos).get() == window) {
706         if (m_prev_active_window_idx == i)
707           m_prev_active_window_idx = UINT32_MAX;
708         else if (m_prev_active_window_idx != UINT32_MAX &&
709                  m_prev_active_window_idx > i)
710           --m_prev_active_window_idx;
711 
712         if (m_curr_active_window_idx == i)
713           m_curr_active_window_idx = UINT32_MAX;
714         else if (m_curr_active_window_idx != UINT32_MAX &&
715                  m_curr_active_window_idx > i)
716           --m_curr_active_window_idx;
717         window->Erase();
718         m_subwindows.erase(pos);
719         m_needs_update = true;
720         if (m_parent)
721           m_parent->Touch();
722         else
723           ::touchwin(stdscr);
724         return true;
725       }
726     }
727     return false;
728   }
729 
FindSubWindow(const char * name)730   WindowSP FindSubWindow(const char *name) {
731     Windows::iterator pos, end = m_subwindows.end();
732     size_t i = 0;
733     for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
734       if ((*pos)->m_name == name)
735         return *pos;
736     }
737     return WindowSP();
738   }
739 
RemoveSubWindows()740   void RemoveSubWindows() {
741     m_curr_active_window_idx = UINT32_MAX;
742     m_prev_active_window_idx = UINT32_MAX;
743     for (Windows::iterator pos = m_subwindows.begin();
744          pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) {
745       (*pos)->Erase();
746     }
747     if (m_parent)
748       m_parent->Touch();
749     else
750       ::touchwin(stdscr);
751   }
752 
753   // Window drawing utilities
DrawTitleBox(const char * title,const char * bottom_message=nullptr)754   void DrawTitleBox(const char *title, const char *bottom_message = nullptr) {
755     attr_t attr = 0;
756     if (IsActive())
757       attr = A_BOLD | COLOR_PAIR(BlackOnWhite);
758     else
759       attr = 0;
760     if (attr)
761       AttributeOn(attr);
762 
763     Box();
764     MoveCursor(3, 0);
765 
766     if (title && title[0]) {
767       PutChar('<');
768       PutCString(title);
769       PutChar('>');
770     }
771 
772     if (bottom_message && bottom_message[0]) {
773       int bottom_message_length = strlen(bottom_message);
774       int x = GetWidth() - 3 - (bottom_message_length + 2);
775 
776       if (x > 0) {
777         MoveCursor(x, GetHeight() - 1);
778         PutChar('[');
779         PutCString(bottom_message);
780         PutChar(']');
781       } else {
782         MoveCursor(1, GetHeight() - 1);
783         PutChar('[');
784         PutCStringTruncated(1, bottom_message);
785       }
786     }
787     if (attr)
788       AttributeOff(attr);
789   }
790 
Draw(bool force)791   virtual void Draw(bool force) {
792     if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force))
793       return;
794 
795     for (auto &subwindow_sp : m_subwindows)
796       subwindow_sp->Draw(force);
797   }
798 
CreateHelpSubwindow()799   bool CreateHelpSubwindow() {
800     if (m_delegate_sp) {
801       const char *text = m_delegate_sp->WindowDelegateGetHelpText();
802       KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp();
803       if ((text && text[0]) || key_help) {
804         std::unique_ptr<HelpDialogDelegate> help_delegate_up(
805             new HelpDialogDelegate(text, key_help));
806         const size_t num_lines = help_delegate_up->GetNumLines();
807         const size_t max_length = help_delegate_up->GetMaxLineLength();
808         Rect bounds = GetBounds();
809         bounds.Inset(1, 1);
810         if (max_length + 4 < static_cast<size_t>(bounds.size.width)) {
811           bounds.origin.x += (bounds.size.width - max_length + 4) / 2;
812           bounds.size.width = max_length + 4;
813         } else {
814           if (bounds.size.width > 100) {
815             const int inset_w = bounds.size.width / 4;
816             bounds.origin.x += inset_w;
817             bounds.size.width -= 2 * inset_w;
818           }
819         }
820 
821         if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) {
822           bounds.origin.y += (bounds.size.height - num_lines + 2) / 2;
823           bounds.size.height = num_lines + 2;
824         } else {
825           if (bounds.size.height > 100) {
826             const int inset_h = bounds.size.height / 4;
827             bounds.origin.y += inset_h;
828             bounds.size.height -= 2 * inset_h;
829           }
830         }
831         WindowSP help_window_sp;
832         Window *parent_window = GetParent();
833         if (parent_window)
834           help_window_sp = parent_window->CreateSubWindow("Help", bounds, true);
835         else
836           help_window_sp = CreateSubWindow("Help", bounds, true);
837         help_window_sp->SetDelegate(
838             WindowDelegateSP(help_delegate_up.release()));
839         return true;
840       }
841     }
842     return false;
843   }
844 
HandleChar(int key)845   virtual HandleCharResult HandleChar(int key) {
846     // Always check the active window first
847     HandleCharResult result = eKeyNotHandled;
848     WindowSP active_window_sp = GetActiveWindow();
849     if (active_window_sp) {
850       result = active_window_sp->HandleChar(key);
851       if (result != eKeyNotHandled)
852         return result;
853     }
854 
855     if (m_delegate_sp) {
856       result = m_delegate_sp->WindowDelegateHandleChar(*this, key);
857       if (result != eKeyNotHandled)
858         return result;
859     }
860 
861     // Then check for any windows that want any keys that weren't handled. This
862     // is typically only for a menubar. Make a copy of the subwindows in case
863     // any HandleChar() functions muck with the subwindows. If we don't do
864     // this, we can crash when iterating over the subwindows.
865     Windows subwindows(m_subwindows);
866     for (auto subwindow_sp : subwindows) {
867       if (!subwindow_sp->m_can_activate) {
868         HandleCharResult result = subwindow_sp->HandleChar(key);
869         if (result != eKeyNotHandled)
870           return result;
871       }
872     }
873 
874     return eKeyNotHandled;
875   }
876 
GetActiveWindow()877   WindowSP GetActiveWindow() {
878     if (!m_subwindows.empty()) {
879       if (m_curr_active_window_idx >= m_subwindows.size()) {
880         if (m_prev_active_window_idx < m_subwindows.size()) {
881           m_curr_active_window_idx = m_prev_active_window_idx;
882           m_prev_active_window_idx = UINT32_MAX;
883         } else if (IsActive()) {
884           m_prev_active_window_idx = UINT32_MAX;
885           m_curr_active_window_idx = UINT32_MAX;
886 
887           // Find first window that wants to be active if this window is active
888           const size_t num_subwindows = m_subwindows.size();
889           for (size_t i = 0; i < num_subwindows; ++i) {
890             if (m_subwindows[i]->GetCanBeActive()) {
891               m_curr_active_window_idx = i;
892               break;
893             }
894           }
895         }
896       }
897 
898       if (m_curr_active_window_idx < m_subwindows.size())
899         return m_subwindows[m_curr_active_window_idx];
900     }
901     return WindowSP();
902   }
903 
GetCanBeActive() const904   bool GetCanBeActive() const { return m_can_activate; }
905 
SetCanBeActive(bool b)906   void SetCanBeActive(bool b) { m_can_activate = b; }
907 
SetDelegate(const WindowDelegateSP & delegate_sp)908   void SetDelegate(const WindowDelegateSP &delegate_sp) {
909     m_delegate_sp = delegate_sp;
910   }
911 
GetParent() const912   Window *GetParent() const { return m_parent; }
913 
IsActive() const914   bool IsActive() const {
915     if (m_parent)
916       return m_parent->GetActiveWindow().get() == this;
917     else
918       return true; // Top level window is always active
919   }
920 
SelectNextWindowAsActive()921   void SelectNextWindowAsActive() {
922     // Move active focus to next window
923     const int num_subwindows = m_subwindows.size();
924     int start_idx = 0;
925     if (m_curr_active_window_idx != UINT32_MAX) {
926       m_prev_active_window_idx = m_curr_active_window_idx;
927       start_idx = m_curr_active_window_idx + 1;
928     }
929     for (int idx = start_idx; idx < num_subwindows; ++idx) {
930       if (m_subwindows[idx]->GetCanBeActive()) {
931         m_curr_active_window_idx = idx;
932         return;
933       }
934     }
935     for (int idx = 0; idx < start_idx; ++idx) {
936       if (m_subwindows[idx]->GetCanBeActive()) {
937         m_curr_active_window_idx = idx;
938         break;
939       }
940     }
941   }
942 
SelectPreviousWindowAsActive()943   void SelectPreviousWindowAsActive() {
944     // Move active focus to previous window
945     const int num_subwindows = m_subwindows.size();
946     int start_idx = num_subwindows - 1;
947     if (m_curr_active_window_idx != UINT32_MAX) {
948       m_prev_active_window_idx = m_curr_active_window_idx;
949       start_idx = m_curr_active_window_idx - 1;
950     }
951     for (int idx = start_idx; idx >= 0; --idx) {
952       if (m_subwindows[idx]->GetCanBeActive()) {
953         m_curr_active_window_idx = idx;
954         return;
955       }
956     }
957     for (int idx = num_subwindows - 1; idx > start_idx; --idx) {
958       if (m_subwindows[idx]->GetCanBeActive()) {
959         m_curr_active_window_idx = idx;
960         break;
961       }
962     }
963   }
964 
GetName() const965   const char *GetName() const { return m_name.c_str(); }
966 
967 protected:
968   std::string m_name;
969   PANEL *m_panel;
970   Window *m_parent;
971   Windows m_subwindows;
972   WindowDelegateSP m_delegate_sp;
973   uint32_t m_curr_active_window_idx;
974   uint32_t m_prev_active_window_idx;
975   bool m_delete;
976   bool m_needs_update;
977   bool m_can_activate;
978   bool m_is_subwin;
979 
980 private:
981   Window(const Window &) = delete;
982   const Window &operator=(const Window &) = delete;
983 };
984 
985 /////////
986 // Forms
987 /////////
988 
989 // A scroll context defines a vertical region that needs to be visible in a
990 // scrolling area. The region is defined by the index of the start and end lines
991 // of the region. The start and end lines may be equal, in which case, the
992 // region is a single line.
993 struct ScrollContext {
994   int start;
995   int end;
996 
ScrollContextcurses::ScrollContext997   ScrollContext(int line) : start(line), end(line) {}
ScrollContextcurses::ScrollContext998   ScrollContext(int _start, int _end) : start(_start), end(_end) {}
999 
Offsetcurses::ScrollContext1000   void Offset(int offset) {
1001     start += offset;
1002     end += offset;
1003   }
1004 };
1005 
1006 class FieldDelegate {
1007 public:
1008   virtual ~FieldDelegate() = default;
1009 
1010   // Returns the number of lines needed to draw the field. The draw method will
1011   // be given a surface that have exactly this number of lines.
1012   virtual int FieldDelegateGetHeight() = 0;
1013 
1014   // Returns the scroll context in the local coordinates of the field. By
1015   // default, the scroll context spans the whole field. Bigger fields with
1016   // internal navigation should override this method to provide a finer context.
1017   // Typical override methods would first get the scroll context of the internal
1018   // element then add the offset of the element in the field.
FieldDelegateGetScrollContext()1019   virtual ScrollContext FieldDelegateGetScrollContext() {
1020     return ScrollContext(0, FieldDelegateGetHeight() - 1);
1021   }
1022 
1023   // Draw the field in the given subpad surface. The surface have a height that
1024   // is equal to the height returned by FieldDelegateGetHeight(). If the field
1025   // is selected in the form window, then is_selected will be true.
1026   virtual void FieldDelegateDraw(Surface &surface, bool is_selected) = 0;
1027 
1028   // Handle the key that wasn't handled by the form window or a container field.
FieldDelegateHandleChar(int key)1029   virtual HandleCharResult FieldDelegateHandleChar(int key) {
1030     return eKeyNotHandled;
1031   }
1032 
1033   // This is executed once the user exists the field, that is, once the user
1034   // navigates to the next or the previous field. This is particularly useful to
1035   // do in-field validation and error setting. Fields with internal navigation
1036   // should call this method on their fields.
FieldDelegateExitCallback()1037   virtual void FieldDelegateExitCallback() { return; }
1038 
1039   // Fields may have internal navigation, for instance, a List Field have
1040   // multiple internal elements, which needs to be navigated. To allow for this
1041   // mechanism, the window shouldn't handle the navigation keys all the time,
1042   // and instead call the key handing method of the selected field. It should
1043   // only handle the navigation keys when the field contains a single element or
1044   // have the last or first element selected depending on if the user is
1045   // navigating forward or backward. Additionally, once a field is selected in
1046   // the forward or backward direction, its first or last internal element
1047   // should be selected. The following methods implements those mechanisms.
1048 
1049   // Returns true if the first element in the field is selected or if the field
1050   // contains a single element.
FieldDelegateOnFirstOrOnlyElement()1051   virtual bool FieldDelegateOnFirstOrOnlyElement() { return true; }
1052 
1053   // Returns true if the last element in the field is selected or if the field
1054   // contains a single element.
FieldDelegateOnLastOrOnlyElement()1055   virtual bool FieldDelegateOnLastOrOnlyElement() { return true; }
1056 
1057   // Select the first element in the field if multiple elements exists.
FieldDelegateSelectFirstElement()1058   virtual void FieldDelegateSelectFirstElement() { return; }
1059 
1060   // Select the last element in the field if multiple elements exists.
FieldDelegateSelectLastElement()1061   virtual void FieldDelegateSelectLastElement() { return; }
1062 
1063   // Returns true if the field has an error, false otherwise.
FieldDelegateHasError()1064   virtual bool FieldDelegateHasError() { return false; }
1065 
FieldDelegateIsVisible()1066   bool FieldDelegateIsVisible() { return m_is_visible; }
1067 
FieldDelegateHide()1068   void FieldDelegateHide() { m_is_visible = false; }
1069 
FieldDelegateShow()1070   void FieldDelegateShow() { m_is_visible = true; }
1071 
1072 protected:
1073   bool m_is_visible = true;
1074 };
1075 
1076 typedef std::unique_ptr<FieldDelegate> FieldDelegateUP;
1077 
1078 class TextFieldDelegate : public FieldDelegate {
1079 public:
TextFieldDelegate(const char * label,const char * content,bool required)1080   TextFieldDelegate(const char *label, const char *content, bool required)
1081       : m_label(label), m_required(required), m_cursor_position(0),
1082         m_first_visibile_char(0) {
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;
1331   // The index of the first visible character in the content.
1332   int m_first_visibile_char;
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), m_choice(0), m_first_visibile_choice(0) {}
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(const std::string & choice)1614   void SetChoice(const std::string &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;
1629   // The index of the first visible choice in the field.
1630   int m_first_visibile_choice;
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->GetName().AsCString());
1640   }
1641 
GetPossiblePluginNames()1642   std::vector<std::string> GetPossiblePluginNames() {
1643     std::vector<std::string> names;
1644     size_t i = 0;
1645     while (auto name = PluginManager::GetPlatformPluginNameAtIndex(i++))
1646       names.push_back(name);
1647     return names;
1648   }
1649 
GetPluginName()1650   std::string GetPluginName() {
1651     std::string plugin_name = GetChoiceContent();
1652     return plugin_name;
1653   }
1654 };
1655 
1656 class ProcessPluginFieldDelegate : public ChoicesFieldDelegate {
1657 public:
ProcessPluginFieldDelegate()1658   ProcessPluginFieldDelegate()
1659       : ChoicesFieldDelegate("Process Plugin", 3, GetPossiblePluginNames()) {}
1660 
GetPossiblePluginNames()1661   std::vector<std::string> GetPossiblePluginNames() {
1662     std::vector<std::string> names;
1663     names.push_back("<default>");
1664 
1665     size_t i = 0;
1666     while (auto name = PluginManager::GetProcessPluginNameAtIndex(i++))
1667       names.push_back(name);
1668     return names;
1669   }
1670 
GetPluginName()1671   std::string GetPluginName() {
1672     std::string plugin_name = GetChoiceContent();
1673     if (plugin_name == "<default>")
1674       return "";
1675     return plugin_name;
1676   }
1677 };
1678 
1679 class LazyBooleanFieldDelegate : public ChoicesFieldDelegate {
1680 public:
LazyBooleanFieldDelegate(const char * label,const char * calculate_label)1681   LazyBooleanFieldDelegate(const char *label, const char *calculate_label)
1682       : ChoicesFieldDelegate(label, 3, GetPossibleOptions(calculate_label)) {}
1683 
1684   static constexpr const char *kNo = "No";
1685   static constexpr const char *kYes = "Yes";
1686 
GetPossibleOptions(const char * calculate_label)1687   std::vector<std::string> GetPossibleOptions(const char *calculate_label) {
1688     std::vector<std::string> options;
1689     options.push_back(calculate_label);
1690     options.push_back(kYes);
1691     options.push_back(kNo);
1692     return options;
1693   }
1694 
GetLazyBoolean()1695   LazyBool GetLazyBoolean() {
1696     std::string choice = GetChoiceContent();
1697     if (choice == kNo)
1698       return eLazyBoolNo;
1699     else if (choice == kYes)
1700       return eLazyBoolYes;
1701     else
1702       return eLazyBoolCalculate;
1703   }
1704 };
1705 
1706 template <class T> class ListFieldDelegate : public FieldDelegate {
1707 public:
ListFieldDelegate(const char * label,T default_field)1708   ListFieldDelegate(const char *label, T default_field)
1709       : m_label(label), m_default_field(default_field), m_selection_index(0),
1710         m_selection_type(SelectionType::NewButton) {}
1711 
1712   // Signify which element is selected. If a field or a remove button is
1713   // selected, then m_selection_index signifies the particular field that
1714   // is selected or the field that the remove button belongs to.
1715   enum class SelectionType { Field, RemoveButton, NewButton };
1716 
1717   // A List field is drawn as a titled box of a number of other fields of the
1718   // same type. Each field has a Remove button next to it that removes the
1719   // corresponding field. Finally, the last line contains a New button to add a
1720   // new field.
1721   //
1722   // __[Label]___________
1723   // | Field 0 [Remove] |
1724   // | Field 1 [Remove] |
1725   // | Field 2 [Remove] |
1726   // |      [New]       |
1727   // |__________________|
1728 
1729   // List fields have two lines for border characters, 1 line for the New
1730   // button, and the total height of the available fields.
FieldDelegateGetHeight()1731   int FieldDelegateGetHeight() override {
1732     // 2 border characters.
1733     int height = 2;
1734     // Total height of the fields.
1735     for (int i = 0; i < GetNumberOfFields(); i++) {
1736       height += m_fields[i].FieldDelegateGetHeight();
1737     }
1738     // A line for the New button.
1739     height++;
1740     return height;
1741   }
1742 
FieldDelegateGetScrollContext()1743   ScrollContext FieldDelegateGetScrollContext() override {
1744     int height = FieldDelegateGetHeight();
1745     if (m_selection_type == SelectionType::NewButton)
1746       return ScrollContext(height - 2, height - 1);
1747 
1748     FieldDelegate &field = m_fields[m_selection_index];
1749     ScrollContext context = field.FieldDelegateGetScrollContext();
1750 
1751     // Start at 1 because of the top border.
1752     int offset = 1;
1753     for (int i = 0; i < m_selection_index; i++) {
1754       offset += m_fields[i].FieldDelegateGetHeight();
1755     }
1756     context.Offset(offset);
1757 
1758     // If the scroll context is touching the top border, include it in the
1759     // context to show the label.
1760     if (context.start == 1)
1761       context.start--;
1762 
1763     // If the scroll context is touching the new button, include it as well as
1764     // the bottom border in the context.
1765     if (context.end == height - 3)
1766       context.end += 2;
1767 
1768     return context;
1769   }
1770 
DrawRemoveButton(Surface & surface,int highlight)1771   void DrawRemoveButton(Surface &surface, int highlight) {
1772     surface.MoveCursor(1, surface.GetHeight() / 2);
1773     if (highlight)
1774       surface.AttributeOn(A_REVERSE);
1775     surface.PutCString("[Remove]");
1776     if (highlight)
1777       surface.AttributeOff(A_REVERSE);
1778   }
1779 
DrawFields(Surface & surface,bool is_selected)1780   void DrawFields(Surface &surface, bool is_selected) {
1781     int line = 0;
1782     int width = surface.GetWidth();
1783     for (int i = 0; i < GetNumberOfFields(); i++) {
1784       int height = m_fields[i].FieldDelegateGetHeight();
1785       Rect bounds = Rect(Point(0, line), Size(width, height));
1786       Rect field_bounds, remove_button_bounds;
1787       bounds.VerticalSplit(bounds.size.width - sizeof(" [Remove]"),
1788                            field_bounds, remove_button_bounds);
1789       Surface field_surface = surface.SubSurface(field_bounds);
1790       Surface remove_button_surface = surface.SubSurface(remove_button_bounds);
1791 
1792       bool is_element_selected = m_selection_index == i && is_selected;
1793       bool is_field_selected =
1794           is_element_selected && m_selection_type == SelectionType::Field;
1795       bool is_remove_button_selected =
1796           is_element_selected &&
1797           m_selection_type == SelectionType::RemoveButton;
1798       m_fields[i].FieldDelegateDraw(field_surface, is_field_selected);
1799       DrawRemoveButton(remove_button_surface, is_remove_button_selected);
1800 
1801       line += height;
1802     }
1803   }
1804 
DrawNewButton(Surface & surface,bool is_selected)1805   void DrawNewButton(Surface &surface, bool is_selected) {
1806     const char *button_text = "[New]";
1807     int x = (surface.GetWidth() - sizeof(button_text) - 1) / 2;
1808     surface.MoveCursor(x, 0);
1809     bool highlight =
1810         is_selected && m_selection_type == SelectionType::NewButton;
1811     if (highlight)
1812       surface.AttributeOn(A_REVERSE);
1813     surface.PutCString(button_text);
1814     if (highlight)
1815       surface.AttributeOff(A_REVERSE);
1816   }
1817 
FieldDelegateDraw(Surface & surface,bool is_selected)1818   void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1819     surface.TitledBox(m_label.c_str());
1820 
1821     Rect content_bounds = surface.GetFrame();
1822     content_bounds.Inset(1, 1);
1823     Rect fields_bounds, new_button_bounds;
1824     content_bounds.HorizontalSplit(content_bounds.size.height - 1,
1825                                    fields_bounds, new_button_bounds);
1826     Surface fields_surface = surface.SubSurface(fields_bounds);
1827     Surface new_button_surface = surface.SubSurface(new_button_bounds);
1828 
1829     DrawFields(fields_surface, is_selected);
1830     DrawNewButton(new_button_surface, is_selected);
1831   }
1832 
AddNewField()1833   void AddNewField() {
1834     m_fields.push_back(m_default_field);
1835     m_selection_index = GetNumberOfFields() - 1;
1836     m_selection_type = SelectionType::Field;
1837     FieldDelegate &field = m_fields[m_selection_index];
1838     field.FieldDelegateSelectFirstElement();
1839   }
1840 
RemoveField()1841   void RemoveField() {
1842     m_fields.erase(m_fields.begin() + m_selection_index);
1843     if (m_selection_index != 0)
1844       m_selection_index--;
1845 
1846     if (GetNumberOfFields() > 0) {
1847       m_selection_type = SelectionType::Field;
1848       FieldDelegate &field = m_fields[m_selection_index];
1849       field.FieldDelegateSelectFirstElement();
1850     } else
1851       m_selection_type = SelectionType::NewButton;
1852   }
1853 
SelectNext(int key)1854   HandleCharResult SelectNext(int key) {
1855     if (m_selection_type == SelectionType::NewButton)
1856       return eKeyNotHandled;
1857 
1858     if (m_selection_type == SelectionType::RemoveButton) {
1859       if (m_selection_index == GetNumberOfFields() - 1) {
1860         m_selection_type = SelectionType::NewButton;
1861         return eKeyHandled;
1862       }
1863       m_selection_index++;
1864       m_selection_type = SelectionType::Field;
1865       FieldDelegate &next_field = m_fields[m_selection_index];
1866       next_field.FieldDelegateSelectFirstElement();
1867       return eKeyHandled;
1868     }
1869 
1870     FieldDelegate &field = m_fields[m_selection_index];
1871     if (!field.FieldDelegateOnLastOrOnlyElement()) {
1872       return field.FieldDelegateHandleChar(key);
1873     }
1874 
1875     field.FieldDelegateExitCallback();
1876 
1877     m_selection_type = SelectionType::RemoveButton;
1878     return eKeyHandled;
1879   }
1880 
SelectPrevious(int key)1881   HandleCharResult SelectPrevious(int key) {
1882     if (FieldDelegateOnFirstOrOnlyElement())
1883       return eKeyNotHandled;
1884 
1885     if (m_selection_type == SelectionType::RemoveButton) {
1886       m_selection_type = SelectionType::Field;
1887       FieldDelegate &field = m_fields[m_selection_index];
1888       field.FieldDelegateSelectLastElement();
1889       return eKeyHandled;
1890     }
1891 
1892     if (m_selection_type == SelectionType::NewButton) {
1893       m_selection_type = SelectionType::RemoveButton;
1894       m_selection_index = GetNumberOfFields() - 1;
1895       return eKeyHandled;
1896     }
1897 
1898     FieldDelegate &field = m_fields[m_selection_index];
1899     if (!field.FieldDelegateOnFirstOrOnlyElement()) {
1900       return field.FieldDelegateHandleChar(key);
1901     }
1902 
1903     field.FieldDelegateExitCallback();
1904 
1905     m_selection_type = SelectionType::RemoveButton;
1906     m_selection_index--;
1907     return eKeyHandled;
1908   }
1909 
1910   // If the last element of the field is selected and it didn't handle the key.
1911   // Select the next field or new button if the selected field is the last one.
SelectNextInList(int key)1912   HandleCharResult SelectNextInList(int key) {
1913     assert(m_selection_type == SelectionType::Field);
1914 
1915     FieldDelegate &field = m_fields[m_selection_index];
1916     if (field.FieldDelegateHandleChar(key) == eKeyHandled)
1917       return eKeyHandled;
1918 
1919     if (!field.FieldDelegateOnLastOrOnlyElement())
1920       return eKeyNotHandled;
1921 
1922     field.FieldDelegateExitCallback();
1923 
1924     if (m_selection_index == GetNumberOfFields() - 1) {
1925       m_selection_type = SelectionType::NewButton;
1926       return eKeyHandled;
1927     }
1928 
1929     m_selection_index++;
1930     FieldDelegate &next_field = m_fields[m_selection_index];
1931     next_field.FieldDelegateSelectFirstElement();
1932     return eKeyHandled;
1933   }
1934 
FieldDelegateHandleChar(int key)1935   HandleCharResult FieldDelegateHandleChar(int key) override {
1936     switch (key) {
1937     case '\r':
1938     case '\n':
1939     case KEY_ENTER:
1940       switch (m_selection_type) {
1941       case SelectionType::NewButton:
1942         AddNewField();
1943         return eKeyHandled;
1944       case SelectionType::RemoveButton:
1945         RemoveField();
1946         return eKeyHandled;
1947       case SelectionType::Field:
1948         return SelectNextInList(key);
1949       }
1950       break;
1951     case '\t':
1952       return SelectNext(key);
1953     case KEY_SHIFT_TAB:
1954       return SelectPrevious(key);
1955     default:
1956       break;
1957     }
1958 
1959     // If the key wasn't handled and one of the fields is selected, pass the key
1960     // to that field.
1961     if (m_selection_type == SelectionType::Field) {
1962       return m_fields[m_selection_index].FieldDelegateHandleChar(key);
1963     }
1964 
1965     return eKeyNotHandled;
1966   }
1967 
FieldDelegateOnLastOrOnlyElement()1968   bool FieldDelegateOnLastOrOnlyElement() override {
1969     if (m_selection_type == SelectionType::NewButton) {
1970       return true;
1971     }
1972     return false;
1973   }
1974 
FieldDelegateOnFirstOrOnlyElement()1975   bool FieldDelegateOnFirstOrOnlyElement() override {
1976     if (m_selection_type == SelectionType::NewButton &&
1977         GetNumberOfFields() == 0)
1978       return true;
1979 
1980     if (m_selection_type == SelectionType::Field && m_selection_index == 0) {
1981       FieldDelegate &field = m_fields[m_selection_index];
1982       return field.FieldDelegateOnFirstOrOnlyElement();
1983     }
1984 
1985     return false;
1986   }
1987 
FieldDelegateSelectFirstElement()1988   void FieldDelegateSelectFirstElement() override {
1989     if (GetNumberOfFields() == 0) {
1990       m_selection_type = SelectionType::NewButton;
1991       return;
1992     }
1993 
1994     m_selection_type = SelectionType::Field;
1995     m_selection_index = 0;
1996   }
1997 
FieldDelegateSelectLastElement()1998   void FieldDelegateSelectLastElement() override {
1999     m_selection_type = SelectionType::NewButton;
2000     return;
2001   }
2002 
GetNumberOfFields()2003   int GetNumberOfFields() { return m_fields.size(); }
2004 
2005   // Returns the form delegate at the current index.
GetField(int index)2006   T &GetField(int index) { return m_fields[index]; }
2007 
2008 protected:
2009   std::string m_label;
2010   // The default field delegate instance from which new field delegates will be
2011   // created though a copy.
2012   T m_default_field;
2013   std::vector<T> m_fields;
2014   int m_selection_index;
2015   // See SelectionType class enum.
2016   SelectionType m_selection_type;
2017 };
2018 
2019 class ArgumentsFieldDelegate : public ListFieldDelegate<TextFieldDelegate> {
2020 public:
ArgumentsFieldDelegate()2021   ArgumentsFieldDelegate()
2022       : ListFieldDelegate("Arguments",
2023                           TextFieldDelegate("Argument", "", false)) {}
2024 
GetArguments()2025   Args GetArguments() {
2026     Args arguments;
2027     for (int i = 0; i < GetNumberOfFields(); i++) {
2028       arguments.AppendArgument(GetField(i).GetText());
2029     }
2030     return arguments;
2031   }
2032 
AddArguments(const Args & arguments)2033   void AddArguments(const Args &arguments) {
2034     for (size_t i = 0; i < arguments.GetArgumentCount(); i++) {
2035       AddNewField();
2036       TextFieldDelegate &field = GetField(GetNumberOfFields() - 1);
2037       field.SetText(arguments.GetArgumentAtIndex(i));
2038     }
2039   }
2040 };
2041 
2042 template <class KeyFieldDelegateType, class ValueFieldDelegateType>
2043 class MappingFieldDelegate : public FieldDelegate {
2044 public:
MappingFieldDelegate(KeyFieldDelegateType key_field,ValueFieldDelegateType value_field)2045   MappingFieldDelegate(KeyFieldDelegateType key_field,
2046                        ValueFieldDelegateType value_field)
2047       : m_key_field(key_field), m_value_field(value_field),
2048         m_selection_type(SelectionType::Key) {}
2049 
2050   // Signify which element is selected. The key field or its value field.
2051   enum class SelectionType { Key, Value };
2052 
2053   // A mapping field is drawn as two text fields with a right arrow in between.
2054   // The first field stores the key of the mapping and the second stores the
2055   // value if the mapping.
2056   //
2057   // __[Key]_____________   __[Value]___________
2058   // |                  | > |                  |
2059   // |__________________|   |__________________|
2060   // - Error message if it exists.
2061 
2062   // The mapping field has a height that is equal to the maximum height between
2063   // the key and value fields.
FieldDelegateGetHeight()2064   int FieldDelegateGetHeight() override {
2065     return std::max(m_key_field.FieldDelegateGetHeight(),
2066                     m_value_field.FieldDelegateGetHeight());
2067   }
2068 
DrawArrow(Surface & surface)2069   void DrawArrow(Surface &surface) {
2070     surface.MoveCursor(0, 1);
2071     surface.PutChar(ACS_RARROW);
2072   }
2073 
FieldDelegateDraw(Surface & surface,bool is_selected)2074   void FieldDelegateDraw(Surface &surface, bool is_selected) override {
2075     Rect bounds = surface.GetFrame();
2076     Rect key_field_bounds, arrow_and_value_field_bounds;
2077     bounds.VerticalSplit(bounds.size.width / 2, key_field_bounds,
2078                          arrow_and_value_field_bounds);
2079     Rect arrow_bounds, value_field_bounds;
2080     arrow_and_value_field_bounds.VerticalSplit(1, arrow_bounds,
2081                                                value_field_bounds);
2082 
2083     Surface key_field_surface = surface.SubSurface(key_field_bounds);
2084     Surface arrow_surface = surface.SubSurface(arrow_bounds);
2085     Surface value_field_surface = surface.SubSurface(value_field_bounds);
2086 
2087     bool key_is_selected =
2088         m_selection_type == SelectionType::Key && is_selected;
2089     m_key_field.FieldDelegateDraw(key_field_surface, key_is_selected);
2090     DrawArrow(arrow_surface);
2091     bool value_is_selected =
2092         m_selection_type == SelectionType::Value && is_selected;
2093     m_value_field.FieldDelegateDraw(value_field_surface, value_is_selected);
2094   }
2095 
SelectNext(int key)2096   HandleCharResult SelectNext(int key) {
2097     if (FieldDelegateOnLastOrOnlyElement())
2098       return eKeyNotHandled;
2099 
2100     if (!m_key_field.FieldDelegateOnLastOrOnlyElement()) {
2101       return m_key_field.FieldDelegateHandleChar(key);
2102     }
2103 
2104     m_key_field.FieldDelegateExitCallback();
2105     m_selection_type = SelectionType::Value;
2106     m_value_field.FieldDelegateSelectFirstElement();
2107     return eKeyHandled;
2108   }
2109 
SelectPrevious(int key)2110   HandleCharResult SelectPrevious(int key) {
2111     if (FieldDelegateOnFirstOrOnlyElement())
2112       return eKeyNotHandled;
2113 
2114     if (!m_value_field.FieldDelegateOnFirstOrOnlyElement()) {
2115       return m_value_field.FieldDelegateHandleChar(key);
2116     }
2117 
2118     m_value_field.FieldDelegateExitCallback();
2119     m_selection_type = SelectionType::Key;
2120     m_key_field.FieldDelegateSelectLastElement();
2121     return eKeyHandled;
2122   }
2123 
2124   // If the value field is selected, pass the key to it. If the key field is
2125   // selected, its last element is selected, and it didn't handle the key, then
2126   // select its corresponding value field.
SelectNextField(int key)2127   HandleCharResult SelectNextField(int key) {
2128     if (m_selection_type == SelectionType::Value) {
2129       return m_value_field.FieldDelegateHandleChar(key);
2130     }
2131 
2132     if (m_key_field.FieldDelegateHandleChar(key) == eKeyHandled)
2133       return eKeyHandled;
2134 
2135     if (!m_key_field.FieldDelegateOnLastOrOnlyElement())
2136       return eKeyNotHandled;
2137 
2138     m_key_field.FieldDelegateExitCallback();
2139     m_selection_type = SelectionType::Value;
2140     m_value_field.FieldDelegateSelectFirstElement();
2141     return eKeyHandled;
2142   }
2143 
FieldDelegateHandleChar(int key)2144   HandleCharResult FieldDelegateHandleChar(int key) override {
2145     switch (key) {
2146     case KEY_RETURN:
2147       return SelectNextField(key);
2148     case '\t':
2149       return SelectNext(key);
2150     case KEY_SHIFT_TAB:
2151       return SelectPrevious(key);
2152     default:
2153       break;
2154     }
2155 
2156     // If the key wasn't handled, pass the key to the selected field.
2157     if (m_selection_type == SelectionType::Key)
2158       return m_key_field.FieldDelegateHandleChar(key);
2159     else
2160       return m_value_field.FieldDelegateHandleChar(key);
2161 
2162     return eKeyNotHandled;
2163   }
2164 
FieldDelegateOnFirstOrOnlyElement()2165   bool FieldDelegateOnFirstOrOnlyElement() override {
2166     return m_selection_type == SelectionType::Key;
2167   }
2168 
FieldDelegateOnLastOrOnlyElement()2169   bool FieldDelegateOnLastOrOnlyElement() override {
2170     return m_selection_type == SelectionType::Value;
2171   }
2172 
FieldDelegateSelectFirstElement()2173   void FieldDelegateSelectFirstElement() override {
2174     m_selection_type = SelectionType::Key;
2175   }
2176 
FieldDelegateSelectLastElement()2177   void FieldDelegateSelectLastElement() override {
2178     m_selection_type = SelectionType::Value;
2179   }
2180 
FieldDelegateHasError()2181   bool FieldDelegateHasError() override {
2182     return m_key_field.FieldDelegateHasError() ||
2183            m_value_field.FieldDelegateHasError();
2184   }
2185 
GetKeyField()2186   KeyFieldDelegateType &GetKeyField() { return m_key_field; }
2187 
GetValueField()2188   ValueFieldDelegateType &GetValueField() { return m_value_field; }
2189 
2190 protected:
2191   KeyFieldDelegateType m_key_field;
2192   ValueFieldDelegateType m_value_field;
2193   // See SelectionType class enum.
2194   SelectionType m_selection_type;
2195 };
2196 
2197 class EnvironmentVariableNameFieldDelegate : public TextFieldDelegate {
2198 public:
EnvironmentVariableNameFieldDelegate(const char * content)2199   EnvironmentVariableNameFieldDelegate(const char *content)
2200       : TextFieldDelegate("Name", content, true) {}
2201 
2202   // Environment variable names can't contain an equal sign.
IsAcceptableChar(int key)2203   bool IsAcceptableChar(int key) override {
2204     return TextFieldDelegate::IsAcceptableChar(key) && key != '=';
2205   }
2206 
GetName()2207   const std::string &GetName() { return m_content; }
2208 };
2209 
2210 class EnvironmentVariableFieldDelegate
2211     : public MappingFieldDelegate<EnvironmentVariableNameFieldDelegate,
2212                                   TextFieldDelegate> {
2213 public:
EnvironmentVariableFieldDelegate()2214   EnvironmentVariableFieldDelegate()
2215       : MappingFieldDelegate(
2216             EnvironmentVariableNameFieldDelegate(""),
2217             TextFieldDelegate("Value", "", /*required=*/false)) {}
2218 
GetName()2219   const std::string &GetName() { return GetKeyField().GetName(); }
2220 
GetValue()2221   const std::string &GetValue() { return GetValueField().GetText(); }
2222 
SetName(const char * name)2223   void SetName(const char *name) { return GetKeyField().SetText(name); }
2224 
SetValue(const char * value)2225   void SetValue(const char *value) { return GetValueField().SetText(value); }
2226 };
2227 
2228 class EnvironmentVariableListFieldDelegate
2229     : public ListFieldDelegate<EnvironmentVariableFieldDelegate> {
2230 public:
EnvironmentVariableListFieldDelegate(const char * label)2231   EnvironmentVariableListFieldDelegate(const char *label)
2232       : ListFieldDelegate(label, EnvironmentVariableFieldDelegate()) {}
2233 
GetEnvironment()2234   Environment GetEnvironment() {
2235     Environment environment;
2236     for (int i = 0; i < GetNumberOfFields(); i++) {
2237       environment.insert(
2238           std::make_pair(GetField(i).GetName(), GetField(i).GetValue()));
2239     }
2240     return environment;
2241   }
2242 
AddEnvironmentVariables(const Environment & environment)2243   void AddEnvironmentVariables(const Environment &environment) {
2244     for (auto &variable : environment) {
2245       AddNewField();
2246       EnvironmentVariableFieldDelegate &field =
2247           GetField(GetNumberOfFields() - 1);
2248       field.SetName(variable.getKey().str().c_str());
2249       field.SetValue(variable.getValue().c_str());
2250     }
2251   }
2252 };
2253 
2254 class FormAction {
2255 public:
FormAction(const char * label,std::function<void (Window &)> action)2256   FormAction(const char *label, std::function<void(Window &)> action)
2257       : m_action(action) {
2258     if (label)
2259       m_label = label;
2260   }
2261 
2262   // Draw a centered [Label].
Draw(Surface & surface,bool is_selected)2263   void Draw(Surface &surface, bool is_selected) {
2264     int x = (surface.GetWidth() - m_label.length()) / 2;
2265     surface.MoveCursor(x, 0);
2266     if (is_selected)
2267       surface.AttributeOn(A_REVERSE);
2268     surface.PutChar('[');
2269     surface.PutCString(m_label.c_str());
2270     surface.PutChar(']');
2271     if (is_selected)
2272       surface.AttributeOff(A_REVERSE);
2273   }
2274 
Execute(Window & window)2275   void Execute(Window &window) { m_action(window); }
2276 
GetLabel()2277   const std::string &GetLabel() { return m_label; }
2278 
2279 protected:
2280   std::string m_label;
2281   std::function<void(Window &)> m_action;
2282 };
2283 
2284 class FormDelegate {
2285 public:
FormDelegate()2286   FormDelegate() {}
2287 
2288   virtual ~FormDelegate() = default;
2289 
2290   virtual std::string GetName() = 0;
2291 
UpdateFieldsVisibility()2292   virtual void UpdateFieldsVisibility() { return; }
2293 
GetField(uint32_t field_index)2294   FieldDelegate *GetField(uint32_t field_index) {
2295     if (field_index < m_fields.size())
2296       return m_fields[field_index].get();
2297     return nullptr;
2298   }
2299 
GetAction(int action_index)2300   FormAction &GetAction(int action_index) { return m_actions[action_index]; }
2301 
GetNumberOfFields()2302   int GetNumberOfFields() { return m_fields.size(); }
2303 
GetNumberOfActions()2304   int GetNumberOfActions() { return m_actions.size(); }
2305 
HasError()2306   bool HasError() { return !m_error.empty(); }
2307 
ClearError()2308   void ClearError() { m_error.clear(); }
2309 
GetError()2310   const std::string &GetError() { return m_error; }
2311 
SetError(const char * error)2312   void SetError(const char *error) { m_error = error; }
2313 
2314   // If all fields are valid, true is returned. Otherwise, an error message is
2315   // set and false is returned. This method is usually called at the start of an
2316   // action that requires valid fields.
CheckFieldsValidity()2317   bool CheckFieldsValidity() {
2318     for (int i = 0; i < GetNumberOfFields(); i++) {
2319       GetField(i)->FieldDelegateExitCallback();
2320       if (GetField(i)->FieldDelegateHasError()) {
2321         SetError("Some fields are invalid!");
2322         return false;
2323       }
2324     }
2325     return true;
2326   }
2327 
2328   // Factory methods to create and add fields of specific types.
2329 
AddTextField(const char * label,const char * content,bool required)2330   TextFieldDelegate *AddTextField(const char *label, const char *content,
2331                                   bool required) {
2332     TextFieldDelegate *delegate =
2333         new TextFieldDelegate(label, content, required);
2334     m_fields.push_back(FieldDelegateUP(delegate));
2335     return delegate;
2336   }
2337 
AddFileField(const char * label,const char * content,bool need_to_exist,bool required)2338   FileFieldDelegate *AddFileField(const char *label, const char *content,
2339                                   bool need_to_exist, bool required) {
2340     FileFieldDelegate *delegate =
2341         new FileFieldDelegate(label, content, need_to_exist, required);
2342     m_fields.push_back(FieldDelegateUP(delegate));
2343     return delegate;
2344   }
2345 
AddDirectoryField(const char * label,const char * content,bool need_to_exist,bool required)2346   DirectoryFieldDelegate *AddDirectoryField(const char *label,
2347                                             const char *content,
2348                                             bool need_to_exist, bool required) {
2349     DirectoryFieldDelegate *delegate =
2350         new DirectoryFieldDelegate(label, content, need_to_exist, required);
2351     m_fields.push_back(FieldDelegateUP(delegate));
2352     return delegate;
2353   }
2354 
AddArchField(const char * label,const char * content,bool required)2355   ArchFieldDelegate *AddArchField(const char *label, const char *content,
2356                                   bool required) {
2357     ArchFieldDelegate *delegate =
2358         new ArchFieldDelegate(label, content, required);
2359     m_fields.push_back(FieldDelegateUP(delegate));
2360     return delegate;
2361   }
2362 
AddIntegerField(const char * label,int content,bool required)2363   IntegerFieldDelegate *AddIntegerField(const char *label, int content,
2364                                         bool required) {
2365     IntegerFieldDelegate *delegate =
2366         new IntegerFieldDelegate(label, content, required);
2367     m_fields.push_back(FieldDelegateUP(delegate));
2368     return delegate;
2369   }
2370 
AddBooleanField(const char * label,bool content)2371   BooleanFieldDelegate *AddBooleanField(const char *label, bool content) {
2372     BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content);
2373     m_fields.push_back(FieldDelegateUP(delegate));
2374     return delegate;
2375   }
2376 
AddLazyBooleanField(const char * label,const char * calculate_label)2377   LazyBooleanFieldDelegate *AddLazyBooleanField(const char *label,
2378                                                 const char *calculate_label) {
2379     LazyBooleanFieldDelegate *delegate =
2380         new LazyBooleanFieldDelegate(label, calculate_label);
2381     m_fields.push_back(FieldDelegateUP(delegate));
2382     return delegate;
2383   }
2384 
AddChoicesField(const char * label,int height,std::vector<std::string> choices)2385   ChoicesFieldDelegate *AddChoicesField(const char *label, int height,
2386                                         std::vector<std::string> choices) {
2387     ChoicesFieldDelegate *delegate =
2388         new ChoicesFieldDelegate(label, height, choices);
2389     m_fields.push_back(FieldDelegateUP(delegate));
2390     return delegate;
2391   }
2392 
AddPlatformPluginField(Debugger & debugger)2393   PlatformPluginFieldDelegate *AddPlatformPluginField(Debugger &debugger) {
2394     PlatformPluginFieldDelegate *delegate =
2395         new PlatformPluginFieldDelegate(debugger);
2396     m_fields.push_back(FieldDelegateUP(delegate));
2397     return delegate;
2398   }
2399 
AddProcessPluginField()2400   ProcessPluginFieldDelegate *AddProcessPluginField() {
2401     ProcessPluginFieldDelegate *delegate = new ProcessPluginFieldDelegate();
2402     m_fields.push_back(FieldDelegateUP(delegate));
2403     return delegate;
2404   }
2405 
2406   template <class T>
AddListField(const char * label,T default_field)2407   ListFieldDelegate<T> *AddListField(const char *label, T default_field) {
2408     ListFieldDelegate<T> *delegate =
2409         new ListFieldDelegate<T>(label, default_field);
2410     m_fields.push_back(FieldDelegateUP(delegate));
2411     return delegate;
2412   }
2413 
AddArgumentsField()2414   ArgumentsFieldDelegate *AddArgumentsField() {
2415     ArgumentsFieldDelegate *delegate = new ArgumentsFieldDelegate();
2416     m_fields.push_back(FieldDelegateUP(delegate));
2417     return delegate;
2418   }
2419 
2420   template <class K, class V>
AddMappingField(K key_field,V value_field)2421   MappingFieldDelegate<K, V> *AddMappingField(K key_field, V value_field) {
2422     MappingFieldDelegate<K, V> *delegate =
2423         new MappingFieldDelegate<K, V>(key_field, value_field);
2424     m_fields.push_back(FieldDelegateUP(delegate));
2425     return delegate;
2426   }
2427 
2428   EnvironmentVariableNameFieldDelegate *
AddEnvironmentVariableNameField(const char * content)2429   AddEnvironmentVariableNameField(const char *content) {
2430     EnvironmentVariableNameFieldDelegate *delegate =
2431         new EnvironmentVariableNameFieldDelegate(content);
2432     m_fields.push_back(FieldDelegateUP(delegate));
2433     return delegate;
2434   }
2435 
AddEnvironmentVariableField()2436   EnvironmentVariableFieldDelegate *AddEnvironmentVariableField() {
2437     EnvironmentVariableFieldDelegate *delegate =
2438         new EnvironmentVariableFieldDelegate();
2439     m_fields.push_back(FieldDelegateUP(delegate));
2440     return delegate;
2441   }
2442 
2443   EnvironmentVariableListFieldDelegate *
AddEnvironmentVariableListField(const char * label)2444   AddEnvironmentVariableListField(const char *label) {
2445     EnvironmentVariableListFieldDelegate *delegate =
2446         new EnvironmentVariableListFieldDelegate(label);
2447     m_fields.push_back(FieldDelegateUP(delegate));
2448     return delegate;
2449   }
2450 
2451   // Factory methods for adding actions.
2452 
AddAction(const char * label,std::function<void (Window &)> action)2453   void AddAction(const char *label, std::function<void(Window &)> action) {
2454     m_actions.push_back(FormAction(label, action));
2455   }
2456 
2457 protected:
2458   std::vector<FieldDelegateUP> m_fields;
2459   std::vector<FormAction> m_actions;
2460   // Optional error message. If empty, form is considered to have no error.
2461   std::string m_error;
2462 };
2463 
2464 typedef std::shared_ptr<FormDelegate> FormDelegateSP;
2465 
2466 class FormWindowDelegate : public WindowDelegate {
2467 public:
FormWindowDelegate(FormDelegateSP & delegate_sp)2468   FormWindowDelegate(FormDelegateSP &delegate_sp)
2469       : m_delegate_sp(delegate_sp), m_selection_index(0),
2470         m_first_visible_line(0) {
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;
2860   // See SelectionType class enum.
2861   SelectionType m_selection_type;
2862   // The first visible line from the pad.
2863   int m_first_visible_line;
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_depentents_options;
3117     load_depentents_options.push_back(kLoadDependentFilesExecOnly);
3118     load_depentents_options.push_back(kLoadDependentFilesYes);
3119     load_depentents_options.push_back(kLoadDependentFilesNo);
3120     return load_depentents_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.GetDirectory() = 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       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       action.Open(STDOUT_FILENO, m_standard_output_field->GetFileSpec(), false,
3510                   true);
3511       launch_info.AppendFileAction(action);
3512     }
3513     if (m_standard_error_field->IsSpecified()) {
3514       action.Open(STDERR_FILENO, m_standard_error_field->GetFileSpec(), false,
3515                   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:
SearcherDelegate()3650   SearcherDelegate() {}
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         m_selected_match(0), m_first_visible_match(0) {
3675     ;
3676   }
3677 
3678   // A completion window is padded by one character from all sides. A text field
3679   // is first drawn for inputting the searcher request, then a list of matches
3680   // are displayed in a scrollable list.
3681   //
3682   // ___<Searcher Window Name>____________________________
3683   // |                                                   |
3684   // | __[Search]_______________________________________ |
3685   // | |                                               | |
3686   // | |_______________________________________________| |
3687   // | - Match 1.                                        |
3688   // | - Match 2.                                        |
3689   // | - ...                                             |
3690   // |                                                   |
3691   // |____________________________[Press Esc to Cancel]__|
3692   //
3693 
3694   // Get the index of the last visible match. Assuming at least one match
3695   // exists.
GetLastVisibleMatch(int height)3696   int GetLastVisibleMatch(int height) {
3697     int index = m_first_visible_match + height;
3698     return std::min(index, m_delegate_sp->GetNumberOfMatches()) - 1;
3699   }
3700 
GetNumberOfVisibleMatches(int height)3701   int GetNumberOfVisibleMatches(int height) {
3702     return GetLastVisibleMatch(height) - m_first_visible_match + 1;
3703   }
3704 
UpdateScrolling(Surface & surface)3705   void UpdateScrolling(Surface &surface) {
3706     if (m_selected_match < m_first_visible_match) {
3707       m_first_visible_match = m_selected_match;
3708       return;
3709     }
3710 
3711     int height = surface.GetHeight();
3712     int last_visible_match = GetLastVisibleMatch(height);
3713     if (m_selected_match > last_visible_match) {
3714       m_first_visible_match = m_selected_match - height + 1;
3715     }
3716   }
3717 
DrawMatches(Surface & surface)3718   void DrawMatches(Surface &surface) {
3719     if (m_delegate_sp->GetNumberOfMatches() == 0)
3720       return;
3721 
3722     UpdateScrolling(surface);
3723 
3724     int count = GetNumberOfVisibleMatches(surface.GetHeight());
3725     for (int i = 0; i < count; i++) {
3726       surface.MoveCursor(1, i);
3727       int current_match = m_first_visible_match + i;
3728       if (current_match == m_selected_match)
3729         surface.AttributeOn(A_REVERSE);
3730       surface.PutCString(
3731           m_delegate_sp->GetMatchTextAtIndex(current_match).c_str());
3732       if (current_match == m_selected_match)
3733         surface.AttributeOff(A_REVERSE);
3734     }
3735   }
3736 
DrawContent(Surface & surface)3737   void DrawContent(Surface &surface) {
3738     Rect content_bounds = surface.GetFrame();
3739     Rect text_field_bounds, matchs_bounds;
3740     content_bounds.HorizontalSplit(m_text_field.FieldDelegateGetHeight(),
3741                                    text_field_bounds, matchs_bounds);
3742     Surface text_field_surface = surface.SubSurface(text_field_bounds);
3743     Surface matches_surface = surface.SubSurface(matchs_bounds);
3744 
3745     m_text_field.FieldDelegateDraw(text_field_surface, true);
3746     DrawMatches(matches_surface);
3747   }
3748 
WindowDelegateDraw(Window & window,bool force)3749   bool WindowDelegateDraw(Window &window, bool force) override {
3750     window.Erase();
3751 
3752     window.DrawTitleBox(window.GetName(), "Press Esc to Cancel");
3753 
3754     Rect content_bounds = window.GetFrame();
3755     content_bounds.Inset(2, 2);
3756     Surface content_surface = window.SubSurface(content_bounds);
3757 
3758     DrawContent(content_surface);
3759     return true;
3760   }
3761 
SelectNext()3762   void SelectNext() {
3763     if (m_selected_match != m_delegate_sp->GetNumberOfMatches() - 1)
3764       m_selected_match++;
3765     return;
3766   }
3767 
SelectPrevious()3768   void SelectPrevious() {
3769     if (m_selected_match != 0)
3770       m_selected_match--;
3771     return;
3772   }
3773 
ExecuteCallback(Window & window)3774   void ExecuteCallback(Window &window) {
3775     m_delegate_sp->ExecuteCallback(m_selected_match);
3776     window.GetParent()->RemoveSubWindow(&window);
3777   }
3778 
UpdateMatches()3779   void UpdateMatches() {
3780     m_delegate_sp->UpdateMatches(m_text_field.GetText());
3781     m_selected_match = 0;
3782   }
3783 
WindowDelegateHandleChar(Window & window,int key)3784   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
3785     switch (key) {
3786     case '\r':
3787     case '\n':
3788     case KEY_ENTER:
3789       ExecuteCallback(window);
3790       return eKeyHandled;
3791     case '\t':
3792     case KEY_DOWN:
3793       SelectNext();
3794       return eKeyHandled;
3795     case KEY_SHIFT_TAB:
3796     case KEY_UP:
3797       SelectPrevious();
3798       return eKeyHandled;
3799     case KEY_ESCAPE:
3800       window.GetParent()->RemoveSubWindow(&window);
3801       return eKeyHandled;
3802     default:
3803       break;
3804     }
3805 
3806     if (m_text_field.FieldDelegateHandleChar(key) == eKeyHandled)
3807       UpdateMatches();
3808 
3809     return eKeyHandled;
3810   }
3811 
3812 protected:
3813   SearcherDelegateSP m_delegate_sp;
3814   TextFieldDelegate m_text_field;
3815   // The index of the currently selected match.
3816   int m_selected_match;
3817   // The index of the first visible match.
3818   int m_first_visible_match;
3819 };
3820 
3821 //////////////////////////////
3822 // Searcher Delegate Instances
3823 //////////////////////////////
3824 
3825 // This is a searcher delegate wrapper around CommandCompletions common
3826 // callbacks. The callbacks are only given the match string. The completion_mask
3827 // can be a combination of CommonCompletionTypes.
3828 class CommonCompletionSearcherDelegate : public SearcherDelegate {
3829 public:
3830   typedef std::function<void(const std::string &)> CallbackType;
3831 
CommonCompletionSearcherDelegate(Debugger & debugger,uint32_t completion_mask,CallbackType callback)3832   CommonCompletionSearcherDelegate(Debugger &debugger, uint32_t completion_mask,
3833                                    CallbackType callback)
3834       : m_debugger(debugger), m_completion_mask(completion_mask),
3835         m_callback(callback) {}
3836 
GetNumberOfMatches()3837   int GetNumberOfMatches() override { return m_matches.GetSize(); }
3838 
GetMatchTextAtIndex(int index)3839   const std::string &GetMatchTextAtIndex(int index) override {
3840     return m_matches[index];
3841   }
3842 
UpdateMatches(const std::string & text)3843   void UpdateMatches(const std::string &text) override {
3844     CompletionResult result;
3845     CompletionRequest request(text.c_str(), text.size(), result);
3846     CommandCompletions::InvokeCommonCompletionCallbacks(
3847         m_debugger.GetCommandInterpreter(), m_completion_mask, request,
3848         nullptr);
3849     result.GetMatches(m_matches);
3850   }
3851 
ExecuteCallback(int match_index)3852   void ExecuteCallback(int match_index) override {
3853     m_callback(m_matches[match_index]);
3854   }
3855 
3856 protected:
3857   Debugger &m_debugger;
3858   // A compound mask from CommonCompletionTypes.
3859   uint32_t m_completion_mask;
3860   // A callback to execute once the user selects a match. The match is passed to
3861   // the callback as a string.
3862   CallbackType m_callback;
3863   StringList m_matches;
3864 };
3865 
3866 ////////
3867 // Menus
3868 ////////
3869 
3870 class MenuDelegate {
3871 public:
3872   virtual ~MenuDelegate() = default;
3873 
3874   virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0;
3875 };
3876 
3877 class Menu : public WindowDelegate {
3878 public:
3879   enum class Type { Invalid, Bar, Item, Separator };
3880 
3881   // Menubar or separator constructor
3882   Menu(Type type);
3883 
3884   // Menuitem constructor
3885   Menu(const char *name, const char *key_name, int key_value,
3886        uint64_t identifier);
3887 
3888   ~Menu() override = default;
3889 
GetDelegate() const3890   const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; }
3891 
SetDelegate(const MenuDelegateSP & delegate_sp)3892   void SetDelegate(const MenuDelegateSP &delegate_sp) {
3893     m_delegate_sp = delegate_sp;
3894   }
3895 
3896   void RecalculateNameLengths();
3897 
3898   void AddSubmenu(const MenuSP &menu_sp);
3899 
3900   int DrawAndRunMenu(Window &window);
3901 
3902   void DrawMenuTitle(Window &window, bool highlight);
3903 
3904   bool WindowDelegateDraw(Window &window, bool force) override;
3905 
3906   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
3907 
ActionPrivate(Menu & menu)3908   MenuActionResult ActionPrivate(Menu &menu) {
3909     MenuActionResult result = MenuActionResult::NotHandled;
3910     if (m_delegate_sp) {
3911       result = m_delegate_sp->MenuDelegateAction(menu);
3912       if (result != MenuActionResult::NotHandled)
3913         return result;
3914     } else if (m_parent) {
3915       result = m_parent->ActionPrivate(menu);
3916       if (result != MenuActionResult::NotHandled)
3917         return result;
3918     }
3919     return m_canned_result;
3920   }
3921 
Action()3922   MenuActionResult Action() {
3923     // Call the recursive action so it can try to handle it with the menu
3924     // delegate, and if not, try our parent menu
3925     return ActionPrivate(*this);
3926   }
3927 
SetCannedResult(MenuActionResult result)3928   void SetCannedResult(MenuActionResult result) { m_canned_result = result; }
3929 
GetSubmenus()3930   Menus &GetSubmenus() { return m_submenus; }
3931 
GetSubmenus() const3932   const Menus &GetSubmenus() const { return m_submenus; }
3933 
GetSelectedSubmenuIndex() const3934   int GetSelectedSubmenuIndex() const { return m_selected; }
3935 
SetSelectedSubmenuIndex(int idx)3936   void SetSelectedSubmenuIndex(int idx) { m_selected = idx; }
3937 
GetType() const3938   Type GetType() const { return m_type; }
3939 
GetStartingColumn() const3940   int GetStartingColumn() const { return m_start_col; }
3941 
SetStartingColumn(int col)3942   void SetStartingColumn(int col) { m_start_col = col; }
3943 
GetKeyValue() const3944   int GetKeyValue() const { return m_key_value; }
3945 
GetName()3946   std::string &GetName() { return m_name; }
3947 
GetDrawWidth() const3948   int GetDrawWidth() const {
3949     return m_max_submenu_name_length + m_max_submenu_key_name_length + 8;
3950   }
3951 
GetIdentifier() const3952   uint64_t GetIdentifier() const { return m_identifier; }
3953 
SetIdentifier(uint64_t identifier)3954   void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
3955 
3956 protected:
3957   std::string m_name;
3958   std::string m_key_name;
3959   uint64_t m_identifier;
3960   Type m_type;
3961   int m_key_value;
3962   int m_start_col;
3963   int m_max_submenu_name_length;
3964   int m_max_submenu_key_name_length;
3965   int m_selected;
3966   Menu *m_parent;
3967   Menus m_submenus;
3968   WindowSP m_menu_window_sp;
3969   MenuActionResult m_canned_result;
3970   MenuDelegateSP m_delegate_sp;
3971 };
3972 
3973 // Menubar or separator constructor
Menu(Type type)3974 Menu::Menu(Type type)
3975     : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0),
3976       m_start_col(0), m_max_submenu_name_length(0),
3977       m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
3978       m_submenus(), m_canned_result(MenuActionResult::NotHandled),
3979       m_delegate_sp() {}
3980 
3981 // Menuitem constructor
Menu(const char * name,const char * key_name,int key_value,uint64_t identifier)3982 Menu::Menu(const char *name, const char *key_name, int key_value,
3983            uint64_t identifier)
3984     : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid),
3985       m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0),
3986       m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
3987       m_submenus(), m_canned_result(MenuActionResult::NotHandled),
3988       m_delegate_sp() {
3989   if (name && name[0]) {
3990     m_name = name;
3991     m_type = Type::Item;
3992     if (key_name && key_name[0])
3993       m_key_name = key_name;
3994   } else {
3995     m_type = Type::Separator;
3996   }
3997 }
3998 
RecalculateNameLengths()3999 void Menu::RecalculateNameLengths() {
4000   m_max_submenu_name_length = 0;
4001   m_max_submenu_key_name_length = 0;
4002   Menus &submenus = GetSubmenus();
4003   const size_t num_submenus = submenus.size();
4004   for (size_t i = 0; i < num_submenus; ++i) {
4005     Menu *submenu = submenus[i].get();
4006     if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size())
4007       m_max_submenu_name_length = submenu->m_name.size();
4008     if (static_cast<size_t>(m_max_submenu_key_name_length) <
4009         submenu->m_key_name.size())
4010       m_max_submenu_key_name_length = submenu->m_key_name.size();
4011   }
4012 }
4013 
AddSubmenu(const MenuSP & menu_sp)4014 void Menu::AddSubmenu(const MenuSP &menu_sp) {
4015   menu_sp->m_parent = this;
4016   if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size())
4017     m_max_submenu_name_length = menu_sp->m_name.size();
4018   if (static_cast<size_t>(m_max_submenu_key_name_length) <
4019       menu_sp->m_key_name.size())
4020     m_max_submenu_key_name_length = menu_sp->m_key_name.size();
4021   m_submenus.push_back(menu_sp);
4022 }
4023 
DrawMenuTitle(Window & window,bool highlight)4024 void Menu::DrawMenuTitle(Window &window, bool highlight) {
4025   if (m_type == Type::Separator) {
4026     window.MoveCursor(0, window.GetCursorY());
4027     window.PutChar(ACS_LTEE);
4028     int width = window.GetWidth();
4029     if (width > 2) {
4030       width -= 2;
4031       for (int i = 0; i < width; ++i)
4032         window.PutChar(ACS_HLINE);
4033     }
4034     window.PutChar(ACS_RTEE);
4035   } else {
4036     const int shortcut_key = m_key_value;
4037     bool underlined_shortcut = false;
4038     const attr_t highlight_attr = A_REVERSE;
4039     if (highlight)
4040       window.AttributeOn(highlight_attr);
4041     if (llvm::isPrint(shortcut_key)) {
4042       size_t lower_pos = m_name.find(tolower(shortcut_key));
4043       size_t upper_pos = m_name.find(toupper(shortcut_key));
4044       const char *name = m_name.c_str();
4045       size_t pos = std::min<size_t>(lower_pos, upper_pos);
4046       if (pos != std::string::npos) {
4047         underlined_shortcut = true;
4048         if (pos > 0) {
4049           window.PutCString(name, pos);
4050           name += pos;
4051         }
4052         const attr_t shortcut_attr = A_UNDERLINE | A_BOLD;
4053         window.AttributeOn(shortcut_attr);
4054         window.PutChar(name[0]);
4055         window.AttributeOff(shortcut_attr);
4056         name++;
4057         if (name[0])
4058           window.PutCString(name);
4059       }
4060     }
4061 
4062     if (!underlined_shortcut) {
4063       window.PutCString(m_name.c_str());
4064     }
4065 
4066     if (highlight)
4067       window.AttributeOff(highlight_attr);
4068 
4069     if (m_key_name.empty()) {
4070       if (!underlined_shortcut && llvm::isPrint(m_key_value)) {
4071         window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
4072         window.Printf(" (%c)", m_key_value);
4073         window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
4074       }
4075     } else {
4076       window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
4077       window.Printf(" (%s)", m_key_name.c_str());
4078       window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
4079     }
4080   }
4081 }
4082 
WindowDelegateDraw(Window & window,bool force)4083 bool Menu::WindowDelegateDraw(Window &window, bool force) {
4084   Menus &submenus = GetSubmenus();
4085   const size_t num_submenus = submenus.size();
4086   const int selected_idx = GetSelectedSubmenuIndex();
4087   Menu::Type menu_type = GetType();
4088   switch (menu_type) {
4089   case Menu::Type::Bar: {
4090     window.SetBackground(BlackOnWhite);
4091     window.MoveCursor(0, 0);
4092     for (size_t i = 0; i < num_submenus; ++i) {
4093       Menu *menu = submenus[i].get();
4094       if (i > 0)
4095         window.PutChar(' ');
4096       menu->SetStartingColumn(window.GetCursorX());
4097       window.PutCString("| ");
4098       menu->DrawMenuTitle(window, false);
4099     }
4100     window.PutCString(" |");
4101   } break;
4102 
4103   case Menu::Type::Item: {
4104     int y = 1;
4105     int x = 3;
4106     // Draw the menu
4107     int cursor_x = 0;
4108     int cursor_y = 0;
4109     window.Erase();
4110     window.SetBackground(BlackOnWhite);
4111     window.Box();
4112     for (size_t i = 0; i < num_submenus; ++i) {
4113       const bool is_selected = (i == static_cast<size_t>(selected_idx));
4114       window.MoveCursor(x, y + i);
4115       if (is_selected) {
4116         // Remember where we want the cursor to be
4117         cursor_x = x - 1;
4118         cursor_y = y + i;
4119       }
4120       submenus[i]->DrawMenuTitle(window, is_selected);
4121     }
4122     window.MoveCursor(cursor_x, cursor_y);
4123   } break;
4124 
4125   default:
4126   case Menu::Type::Separator:
4127     break;
4128   }
4129   return true; // Drawing handled...
4130 }
4131 
WindowDelegateHandleChar(Window & window,int key)4132 HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) {
4133   HandleCharResult result = eKeyNotHandled;
4134 
4135   Menus &submenus = GetSubmenus();
4136   const size_t num_submenus = submenus.size();
4137   const int selected_idx = GetSelectedSubmenuIndex();
4138   Menu::Type menu_type = GetType();
4139   if (menu_type == Menu::Type::Bar) {
4140     MenuSP run_menu_sp;
4141     switch (key) {
4142     case KEY_DOWN:
4143     case KEY_UP:
4144       // Show last menu or first menu
4145       if (selected_idx < static_cast<int>(num_submenus))
4146         run_menu_sp = submenus[selected_idx];
4147       else if (!submenus.empty())
4148         run_menu_sp = submenus.front();
4149       result = eKeyHandled;
4150       break;
4151 
4152     case KEY_RIGHT:
4153       ++m_selected;
4154       if (m_selected >= static_cast<int>(num_submenus))
4155         m_selected = 0;
4156       if (m_selected < static_cast<int>(num_submenus))
4157         run_menu_sp = submenus[m_selected];
4158       else if (!submenus.empty())
4159         run_menu_sp = submenus.front();
4160       result = eKeyHandled;
4161       break;
4162 
4163     case KEY_LEFT:
4164       --m_selected;
4165       if (m_selected < 0)
4166         m_selected = num_submenus - 1;
4167       if (m_selected < static_cast<int>(num_submenus))
4168         run_menu_sp = submenus[m_selected];
4169       else if (!submenus.empty())
4170         run_menu_sp = submenus.front();
4171       result = eKeyHandled;
4172       break;
4173 
4174     default:
4175       for (size_t i = 0; i < num_submenus; ++i) {
4176         if (submenus[i]->GetKeyValue() == key) {
4177           SetSelectedSubmenuIndex(i);
4178           run_menu_sp = submenus[i];
4179           result = eKeyHandled;
4180           break;
4181         }
4182       }
4183       break;
4184     }
4185 
4186     if (run_menu_sp) {
4187       // Run the action on this menu in case we need to populate the menu with
4188       // dynamic content and also in case check marks, and any other menu
4189       // decorations need to be calculated
4190       if (run_menu_sp->Action() == MenuActionResult::Quit)
4191         return eQuitApplication;
4192 
4193       Rect menu_bounds;
4194       menu_bounds.origin.x = run_menu_sp->GetStartingColumn();
4195       menu_bounds.origin.y = 1;
4196       menu_bounds.size.width = run_menu_sp->GetDrawWidth();
4197       menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2;
4198       if (m_menu_window_sp)
4199         window.GetParent()->RemoveSubWindow(m_menu_window_sp.get());
4200 
4201       m_menu_window_sp = window.GetParent()->CreateSubWindow(
4202           run_menu_sp->GetName().c_str(), menu_bounds, true);
4203       m_menu_window_sp->SetDelegate(run_menu_sp);
4204     }
4205   } else if (menu_type == Menu::Type::Item) {
4206     switch (key) {
4207     case KEY_DOWN:
4208       if (m_submenus.size() > 1) {
4209         const int start_select = m_selected;
4210         while (++m_selected != start_select) {
4211           if (static_cast<size_t>(m_selected) >= num_submenus)
4212             m_selected = 0;
4213           if (m_submenus[m_selected]->GetType() == Type::Separator)
4214             continue;
4215           else
4216             break;
4217         }
4218         return eKeyHandled;
4219       }
4220       break;
4221 
4222     case KEY_UP:
4223       if (m_submenus.size() > 1) {
4224         const int start_select = m_selected;
4225         while (--m_selected != start_select) {
4226           if (m_selected < static_cast<int>(0))
4227             m_selected = num_submenus - 1;
4228           if (m_submenus[m_selected]->GetType() == Type::Separator)
4229             continue;
4230           else
4231             break;
4232         }
4233         return eKeyHandled;
4234       }
4235       break;
4236 
4237     case KEY_RETURN:
4238       if (static_cast<size_t>(selected_idx) < num_submenus) {
4239         if (submenus[selected_idx]->Action() == MenuActionResult::Quit)
4240           return eQuitApplication;
4241         window.GetParent()->RemoveSubWindow(&window);
4242         return eKeyHandled;
4243       }
4244       break;
4245 
4246     case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in
4247                      // case other chars are entered for escaped sequences
4248       window.GetParent()->RemoveSubWindow(&window);
4249       return eKeyHandled;
4250 
4251     default:
4252       for (size_t i = 0; i < num_submenus; ++i) {
4253         Menu *menu = submenus[i].get();
4254         if (menu->GetKeyValue() == key) {
4255           SetSelectedSubmenuIndex(i);
4256           window.GetParent()->RemoveSubWindow(&window);
4257           if (menu->Action() == MenuActionResult::Quit)
4258             return eQuitApplication;
4259           return eKeyHandled;
4260         }
4261       }
4262       break;
4263     }
4264   } else if (menu_type == Menu::Type::Separator) {
4265   }
4266   return result;
4267 }
4268 
4269 class Application {
4270 public:
Application(FILE * in,FILE * out)4271   Application(FILE *in, FILE *out)
4272       : m_window_sp(), m_screen(nullptr), m_in(in), m_out(out) {}
4273 
~Application()4274   ~Application() {
4275     m_window_delegates.clear();
4276     m_window_sp.reset();
4277     if (m_screen) {
4278       ::delscreen(m_screen);
4279       m_screen = nullptr;
4280     }
4281   }
4282 
Initialize()4283   void Initialize() {
4284     m_screen = ::newterm(nullptr, m_out, m_in);
4285     ::start_color();
4286     ::curs_set(0);
4287     ::noecho();
4288     ::keypad(stdscr, TRUE);
4289   }
4290 
Terminate()4291   void Terminate() { ::endwin(); }
4292 
Run(Debugger & debugger)4293   void Run(Debugger &debugger) {
4294     bool done = false;
4295     int delay_in_tenths_of_a_second = 1;
4296 
4297     // Alas the threading model in curses is a bit lame so we need to resort
4298     // to polling every 0.5 seconds. We could poll for stdin ourselves and
4299     // then pass the keys down but then we need to translate all of the escape
4300     // sequences ourselves. So we resort to polling for input because we need
4301     // to receive async process events while in this loop.
4302 
4303     halfdelay(delay_in_tenths_of_a_second); // Poll using some number of
4304                                             // tenths of seconds seconds when
4305                                             // calling Window::GetChar()
4306 
4307     ListenerSP listener_sp(
4308         Listener::MakeListener("lldb.IOHandler.curses.Application"));
4309     ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass());
4310     debugger.EnableForwardEvents(listener_sp);
4311 
4312     m_update_screen = true;
4313 #if defined(__APPLE__)
4314     std::deque<int> escape_chars;
4315 #endif
4316 
4317     while (!done) {
4318       if (m_update_screen) {
4319         m_window_sp->Draw(false);
4320         // All windows should be calling Window::DeferredRefresh() instead of
4321         // Window::Refresh() so we can do a single update and avoid any screen
4322         // blinking
4323         update_panels();
4324 
4325         // Cursor hiding isn't working on MacOSX, so hide it in the top left
4326         // corner
4327         m_window_sp->MoveCursor(0, 0);
4328 
4329         doupdate();
4330         m_update_screen = false;
4331       }
4332 
4333 #if defined(__APPLE__)
4334       // Terminal.app doesn't map its function keys correctly, F1-F4 default
4335       // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if
4336       // possible
4337       int ch;
4338       if (escape_chars.empty())
4339         ch = m_window_sp->GetChar();
4340       else {
4341         ch = escape_chars.front();
4342         escape_chars.pop_front();
4343       }
4344       if (ch == KEY_ESCAPE) {
4345         int ch2 = m_window_sp->GetChar();
4346         if (ch2 == 'O') {
4347           int ch3 = m_window_sp->GetChar();
4348           switch (ch3) {
4349           case 'P':
4350             ch = KEY_F(1);
4351             break;
4352           case 'Q':
4353             ch = KEY_F(2);
4354             break;
4355           case 'R':
4356             ch = KEY_F(3);
4357             break;
4358           case 'S':
4359             ch = KEY_F(4);
4360             break;
4361           default:
4362             escape_chars.push_back(ch2);
4363             if (ch3 != -1)
4364               escape_chars.push_back(ch3);
4365             break;
4366           }
4367         } else if (ch2 != -1)
4368           escape_chars.push_back(ch2);
4369       }
4370 #else
4371       int ch = m_window_sp->GetChar();
4372 
4373 #endif
4374       if (ch == -1) {
4375         if (feof(m_in) || ferror(m_in)) {
4376           done = true;
4377         } else {
4378           // Just a timeout from using halfdelay(), check for events
4379           EventSP event_sp;
4380           while (listener_sp->PeekAtNextEvent()) {
4381             listener_sp->GetEvent(event_sp, std::chrono::seconds(0));
4382 
4383             if (event_sp) {
4384               Broadcaster *broadcaster = event_sp->GetBroadcaster();
4385               if (broadcaster) {
4386                 // uint32_t event_type = event_sp->GetType();
4387                 ConstString broadcaster_class(
4388                     broadcaster->GetBroadcasterClass());
4389                 if (broadcaster_class == broadcaster_class_process) {
4390                   m_update_screen = true;
4391                   continue; // Don't get any key, just update our view
4392                 }
4393               }
4394             }
4395           }
4396         }
4397       } else {
4398         HandleCharResult key_result = m_window_sp->HandleChar(ch);
4399         switch (key_result) {
4400         case eKeyHandled:
4401           m_update_screen = true;
4402           break;
4403         case eKeyNotHandled:
4404           if (ch == 12) { // Ctrl+L, force full redraw
4405             redrawwin(m_window_sp->get());
4406             m_update_screen = true;
4407           }
4408           break;
4409         case eQuitApplication:
4410           done = true;
4411           break;
4412         }
4413       }
4414     }
4415 
4416     debugger.CancelForwardEvents(listener_sp);
4417   }
4418 
GetMainWindow()4419   WindowSP &GetMainWindow() {
4420     if (!m_window_sp)
4421       m_window_sp = std::make_shared<Window>("main", stdscr, false);
4422     return m_window_sp;
4423   }
4424 
TerminalSizeChanged()4425   void TerminalSizeChanged() {
4426     ::endwin();
4427     ::refresh();
4428     Rect content_bounds = m_window_sp->GetFrame();
4429     m_window_sp->SetBounds(content_bounds);
4430     if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar"))
4431       menubar_window_sp->SetBounds(content_bounds.MakeMenuBar());
4432     if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status"))
4433       status_window_sp->SetBounds(content_bounds.MakeStatusBar());
4434 
4435     WindowSP source_window_sp = m_window_sp->FindSubWindow("Source");
4436     WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables");
4437     WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers");
4438     WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads");
4439 
4440     Rect threads_bounds;
4441     Rect source_variables_bounds;
4442     content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
4443                                            threads_bounds);
4444     if (threads_window_sp)
4445       threads_window_sp->SetBounds(threads_bounds);
4446     else
4447       source_variables_bounds = content_bounds;
4448 
4449     Rect source_bounds;
4450     Rect variables_registers_bounds;
4451     source_variables_bounds.HorizontalSplitPercentage(
4452         0.70, source_bounds, variables_registers_bounds);
4453     if (variables_window_sp || registers_window_sp) {
4454       if (variables_window_sp && registers_window_sp) {
4455         Rect variables_bounds;
4456         Rect registers_bounds;
4457         variables_registers_bounds.VerticalSplitPercentage(
4458             0.50, variables_bounds, registers_bounds);
4459         variables_window_sp->SetBounds(variables_bounds);
4460         registers_window_sp->SetBounds(registers_bounds);
4461       } else if (variables_window_sp) {
4462         variables_window_sp->SetBounds(variables_registers_bounds);
4463       } else {
4464         registers_window_sp->SetBounds(variables_registers_bounds);
4465       }
4466     } else {
4467       source_bounds = source_variables_bounds;
4468     }
4469 
4470     source_window_sp->SetBounds(source_bounds);
4471 
4472     touchwin(stdscr);
4473     redrawwin(m_window_sp->get());
4474     m_update_screen = true;
4475   }
4476 
4477 protected:
4478   WindowSP m_window_sp;
4479   WindowDelegates m_window_delegates;
4480   SCREEN *m_screen;
4481   FILE *m_in;
4482   FILE *m_out;
4483   bool m_update_screen = false;
4484 };
4485 
4486 } // namespace curses
4487 
4488 using namespace curses;
4489 
4490 struct Row {
4491   ValueObjectUpdater value;
4492   Row *parent;
4493   // The process stop ID when the children were calculated.
4494   uint32_t children_stop_id = 0;
4495   int row_idx = 0;
4496   int x = 1;
4497   int y = 1;
4498   bool might_have_children;
4499   bool expanded = false;
4500   bool calculated_children = false;
4501   std::vector<Row> children;
4502 
RowRow4503   Row(const ValueObjectSP &v, Row *p)
4504       : value(v), parent(p),
4505         might_have_children(v ? v->MightHaveChildren() : false) {}
4506 
GetDepthRow4507   size_t GetDepth() const {
4508     if (parent)
4509       return 1 + parent->GetDepth();
4510     return 0;
4511   }
4512 
ExpandRow4513   void Expand() { expanded = true; }
4514 
GetChildrenRow4515   std::vector<Row> &GetChildren() {
4516     ProcessSP process_sp = value.GetProcessSP();
4517     auto stop_id = process_sp->GetStopID();
4518     if (process_sp && stop_id != children_stop_id) {
4519       children_stop_id = stop_id;
4520       calculated_children = false;
4521     }
4522     if (!calculated_children) {
4523       children.clear();
4524       calculated_children = true;
4525       ValueObjectSP valobj = value.GetSP();
4526       if (valobj) {
4527         const size_t num_children = valobj->GetNumChildren();
4528         for (size_t i = 0; i < num_children; ++i) {
4529           children.push_back(Row(valobj->GetChildAtIndex(i, true), this));
4530         }
4531       }
4532     }
4533     return children;
4534   }
4535 
UnexpandRow4536   void Unexpand() {
4537     expanded = false;
4538     calculated_children = false;
4539     children.clear();
4540   }
4541 
DrawTreeRow4542   void DrawTree(Window &window) {
4543     if (parent)
4544       parent->DrawTreeForChild(window, this, 0);
4545 
4546     if (might_have_children) {
4547       // It we can get UTF8 characters to work we should try to use the
4548       // "symbol" UTF8 string below
4549       //            const char *symbol = "";
4550       //            if (row.expanded)
4551       //                symbol = "\xe2\x96\xbd ";
4552       //            else
4553       //                symbol = "\xe2\x96\xb7 ";
4554       //            window.PutCString (symbol);
4555 
4556       // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v'
4557       // or '>' character...
4558       //            if (expanded)
4559       //                window.PutChar (ACS_DARROW);
4560       //            else
4561       //                window.PutChar (ACS_RARROW);
4562       // Since we can't find any good looking right arrow/down arrow symbols,
4563       // just use a diamond...
4564       window.PutChar(ACS_DIAMOND);
4565       window.PutChar(ACS_HLINE);
4566     }
4567   }
4568 
DrawTreeForChildRow4569   void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) {
4570     if (parent)
4571       parent->DrawTreeForChild(window, this, reverse_depth + 1);
4572 
4573     if (&GetChildren().back() == child) {
4574       // Last child
4575       if (reverse_depth == 0) {
4576         window.PutChar(ACS_LLCORNER);
4577         window.PutChar(ACS_HLINE);
4578       } else {
4579         window.PutChar(' ');
4580         window.PutChar(' ');
4581       }
4582     } else {
4583       if (reverse_depth == 0) {
4584         window.PutChar(ACS_LTEE);
4585         window.PutChar(ACS_HLINE);
4586       } else {
4587         window.PutChar(ACS_VLINE);
4588         window.PutChar(' ');
4589       }
4590     }
4591   }
4592 };
4593 
4594 struct DisplayOptions {
4595   bool show_types;
4596 };
4597 
4598 class TreeItem;
4599 
4600 class TreeDelegate {
4601 public:
4602   TreeDelegate() = default;
4603   virtual ~TreeDelegate() = default;
4604 
4605   virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0;
4606   virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0;
TreeDelegateUpdateSelection(TreeItem & root,int & selection_index,TreeItem * & selected_item)4607   virtual void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
4608                                            TreeItem *&selected_item) {
4609     return;
4610   }
4611   // This is invoked when a tree item is selected. If true is returned, the
4612   // views are updated.
4613   virtual bool TreeDelegateItemSelected(TreeItem &item) = 0;
TreeDelegateExpandRootByDefault()4614   virtual bool TreeDelegateExpandRootByDefault() { return false; }
4615   // This is mostly useful for root tree delegates. If false is returned,
4616   // drawing will be skipped completely. This is needed, for instance, in
4617   // skipping drawing of the threads tree if there is no running process.
TreeDelegateShouldDraw()4618   virtual bool TreeDelegateShouldDraw() { return true; }
4619 };
4620 
4621 typedef std::shared_ptr<TreeDelegate> TreeDelegateSP;
4622 
4623 class TreeItem {
4624 public:
TreeItem(TreeItem * parent,TreeDelegate & delegate,bool might_have_children)4625   TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children)
4626       : m_parent(parent), m_delegate(delegate), m_user_data(nullptr),
4627         m_identifier(0), m_row_idx(-1), m_children(),
4628         m_might_have_children(might_have_children), m_is_expanded(false) {
4629     if (m_parent == nullptr)
4630       m_is_expanded = m_delegate.TreeDelegateExpandRootByDefault();
4631   }
4632 
operator =(const TreeItem & rhs)4633   TreeItem &operator=(const TreeItem &rhs) {
4634     if (this != &rhs) {
4635       m_parent = rhs.m_parent;
4636       m_delegate = rhs.m_delegate;
4637       m_user_data = rhs.m_user_data;
4638       m_identifier = rhs.m_identifier;
4639       m_row_idx = rhs.m_row_idx;
4640       m_children = rhs.m_children;
4641       m_might_have_children = rhs.m_might_have_children;
4642       m_is_expanded = rhs.m_is_expanded;
4643     }
4644     return *this;
4645   }
4646 
4647   TreeItem(const TreeItem &) = default;
4648 
GetDepth() const4649   size_t GetDepth() const {
4650     if (m_parent)
4651       return 1 + m_parent->GetDepth();
4652     return 0;
4653   }
4654 
GetRowIndex() const4655   int GetRowIndex() const { return m_row_idx; }
4656 
ClearChildren()4657   void ClearChildren() { m_children.clear(); }
4658 
Resize(size_t n,const TreeItem & t)4659   void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); }
4660 
operator [](size_t i)4661   TreeItem &operator[](size_t i) { return m_children[i]; }
4662 
SetRowIndex(int row_idx)4663   void SetRowIndex(int row_idx) { m_row_idx = row_idx; }
4664 
GetNumChildren()4665   size_t GetNumChildren() {
4666     m_delegate.TreeDelegateGenerateChildren(*this);
4667     return m_children.size();
4668   }
4669 
ItemWasSelected()4670   void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); }
4671 
CalculateRowIndexes(int & row_idx)4672   void CalculateRowIndexes(int &row_idx) {
4673     SetRowIndex(row_idx);
4674     ++row_idx;
4675 
4676     const bool expanded = IsExpanded();
4677 
4678     // The root item must calculate its children, or we must calculate the
4679     // number of children if the item is expanded
4680     if (m_parent == nullptr || expanded)
4681       GetNumChildren();
4682 
4683     for (auto &item : m_children) {
4684       if (expanded)
4685         item.CalculateRowIndexes(row_idx);
4686       else
4687         item.SetRowIndex(-1);
4688     }
4689   }
4690 
GetParent()4691   TreeItem *GetParent() { return m_parent; }
4692 
IsExpanded() const4693   bool IsExpanded() const { return m_is_expanded; }
4694 
Expand()4695   void Expand() { m_is_expanded = true; }
4696 
Unexpand()4697   void Unexpand() { m_is_expanded = false; }
4698 
Draw(Window & window,const int first_visible_row,const uint32_t selected_row_idx,int & row_idx,int & num_rows_left)4699   bool Draw(Window &window, const int first_visible_row,
4700             const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) {
4701     if (num_rows_left <= 0)
4702       return false;
4703 
4704     if (m_row_idx >= first_visible_row) {
4705       window.MoveCursor(2, row_idx + 1);
4706 
4707       if (m_parent)
4708         m_parent->DrawTreeForChild(window, this, 0);
4709 
4710       if (m_might_have_children) {
4711         // It we can get UTF8 characters to work we should try to use the
4712         // "symbol" UTF8 string below
4713         //            const char *symbol = "";
4714         //            if (row.expanded)
4715         //                symbol = "\xe2\x96\xbd ";
4716         //            else
4717         //                symbol = "\xe2\x96\xb7 ";
4718         //            window.PutCString (symbol);
4719 
4720         // The ACS_DARROW and ACS_RARROW don't look very nice they are just a
4721         // 'v' or '>' character...
4722         //            if (expanded)
4723         //                window.PutChar (ACS_DARROW);
4724         //            else
4725         //                window.PutChar (ACS_RARROW);
4726         // Since we can't find any good looking right arrow/down arrow symbols,
4727         // just use a diamond...
4728         window.PutChar(ACS_DIAMOND);
4729         window.PutChar(ACS_HLINE);
4730       }
4731       bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) &&
4732                        window.IsActive();
4733 
4734       if (highlight)
4735         window.AttributeOn(A_REVERSE);
4736 
4737       m_delegate.TreeDelegateDrawTreeItem(*this, window);
4738 
4739       if (highlight)
4740         window.AttributeOff(A_REVERSE);
4741       ++row_idx;
4742       --num_rows_left;
4743     }
4744 
4745     if (num_rows_left <= 0)
4746       return false; // We are done drawing...
4747 
4748     if (IsExpanded()) {
4749       for (auto &item : m_children) {
4750         // If we displayed all the rows and item.Draw() returns false we are
4751         // done drawing and can exit this for loop
4752         if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx,
4753                        num_rows_left))
4754           break;
4755       }
4756     }
4757     return num_rows_left >= 0; // Return true if not done drawing yet
4758   }
4759 
DrawTreeForChild(Window & window,TreeItem * child,uint32_t reverse_depth)4760   void DrawTreeForChild(Window &window, TreeItem *child,
4761                         uint32_t reverse_depth) {
4762     if (m_parent)
4763       m_parent->DrawTreeForChild(window, this, reverse_depth + 1);
4764 
4765     if (&m_children.back() == child) {
4766       // Last child
4767       if (reverse_depth == 0) {
4768         window.PutChar(ACS_LLCORNER);
4769         window.PutChar(ACS_HLINE);
4770       } else {
4771         window.PutChar(' ');
4772         window.PutChar(' ');
4773       }
4774     } else {
4775       if (reverse_depth == 0) {
4776         window.PutChar(ACS_LTEE);
4777         window.PutChar(ACS_HLINE);
4778       } else {
4779         window.PutChar(ACS_VLINE);
4780         window.PutChar(' ');
4781       }
4782     }
4783   }
4784 
GetItemForRowIndex(uint32_t row_idx)4785   TreeItem *GetItemForRowIndex(uint32_t row_idx) {
4786     if (static_cast<uint32_t>(m_row_idx) == row_idx)
4787       return this;
4788     if (m_children.empty())
4789       return nullptr;
4790     if (IsExpanded()) {
4791       for (auto &item : m_children) {
4792         TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx);
4793         if (selected_item_ptr)
4794           return selected_item_ptr;
4795       }
4796     }
4797     return nullptr;
4798   }
4799 
GetUserData() const4800   void *GetUserData() const { return m_user_data; }
4801 
SetUserData(void * user_data)4802   void SetUserData(void *user_data) { m_user_data = user_data; }
4803 
GetIdentifier() const4804   uint64_t GetIdentifier() const { return m_identifier; }
4805 
SetIdentifier(uint64_t identifier)4806   void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
4807 
GetText() const4808   const std::string &GetText() const { return m_text; }
4809 
SetText(const char * text)4810   void SetText(const char *text) {
4811     if (text == nullptr) {
4812       m_text.clear();
4813       return;
4814     }
4815     m_text = text;
4816   }
4817 
SetMightHaveChildren(bool b)4818   void SetMightHaveChildren(bool b) { m_might_have_children = b; }
4819 
4820 protected:
4821   TreeItem *m_parent;
4822   TreeDelegate &m_delegate;
4823   void *m_user_data;
4824   uint64_t m_identifier;
4825   std::string m_text;
4826   int m_row_idx; // Zero based visible row index, -1 if not visible or for the
4827                  // root item
4828   std::vector<TreeItem> m_children;
4829   bool m_might_have_children;
4830   bool m_is_expanded;
4831 };
4832 
4833 class TreeWindowDelegate : public WindowDelegate {
4834 public:
TreeWindowDelegate(Debugger & debugger,const TreeDelegateSP & delegate_sp)4835   TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp)
4836       : m_debugger(debugger), m_delegate_sp(delegate_sp),
4837         m_root(nullptr, *delegate_sp, true), m_selected_item(nullptr),
4838         m_num_rows(0), m_selected_row_idx(0), m_first_visible_row(0),
4839         m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {}
4840 
NumVisibleRows() const4841   int NumVisibleRows() const { return m_max_y - m_min_y; }
4842 
WindowDelegateDraw(Window & window,bool force)4843   bool WindowDelegateDraw(Window &window, bool force) override {
4844     m_min_x = 2;
4845     m_min_y = 1;
4846     m_max_x = window.GetWidth() - 1;
4847     m_max_y = window.GetHeight() - 1;
4848 
4849     window.Erase();
4850     window.DrawTitleBox(window.GetName());
4851 
4852     if (!m_delegate_sp->TreeDelegateShouldDraw()) {
4853       m_selected_item = nullptr;
4854       return true;
4855     }
4856 
4857     const int num_visible_rows = NumVisibleRows();
4858     m_num_rows = 0;
4859     m_root.CalculateRowIndexes(m_num_rows);
4860     m_delegate_sp->TreeDelegateUpdateSelection(m_root, m_selected_row_idx,
4861                                                m_selected_item);
4862 
4863     // If we unexpanded while having something selected our total number of
4864     // rows is less than the num visible rows, then make sure we show all the
4865     // rows by setting the first visible row accordingly.
4866     if (m_first_visible_row > 0 && m_num_rows < num_visible_rows)
4867       m_first_visible_row = 0;
4868 
4869     // Make sure the selected row is always visible
4870     if (m_selected_row_idx < m_first_visible_row)
4871       m_first_visible_row = m_selected_row_idx;
4872     else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
4873       m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
4874 
4875     int row_idx = 0;
4876     int num_rows_left = num_visible_rows;
4877     m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx,
4878                 num_rows_left);
4879     // Get the selected row
4880     m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4881 
4882     return true; // Drawing handled
4883   }
4884 
WindowDelegateGetHelpText()4885   const char *WindowDelegateGetHelpText() override {
4886     return "Thread window keyboard shortcuts:";
4887   }
4888 
WindowDelegateGetKeyHelp()4889   KeyHelp *WindowDelegateGetKeyHelp() override {
4890     static curses::KeyHelp g_source_view_key_help[] = {
4891         {KEY_UP, "Select previous item"},
4892         {KEY_DOWN, "Select next item"},
4893         {KEY_RIGHT, "Expand the selected item"},
4894         {KEY_LEFT,
4895          "Unexpand the selected item or select parent if not expanded"},
4896         {KEY_PPAGE, "Page up"},
4897         {KEY_NPAGE, "Page down"},
4898         {'h', "Show help dialog"},
4899         {' ', "Toggle item expansion"},
4900         {',', "Page up"},
4901         {'.', "Page down"},
4902         {'\0', nullptr}};
4903     return g_source_view_key_help;
4904   }
4905 
WindowDelegateHandleChar(Window & window,int c)4906   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
4907     switch (c) {
4908     case ',':
4909     case KEY_PPAGE:
4910       // Page up key
4911       if (m_first_visible_row > 0) {
4912         if (m_first_visible_row > m_max_y)
4913           m_first_visible_row -= m_max_y;
4914         else
4915           m_first_visible_row = 0;
4916         m_selected_row_idx = m_first_visible_row;
4917         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4918         if (m_selected_item)
4919           m_selected_item->ItemWasSelected();
4920       }
4921       return eKeyHandled;
4922 
4923     case '.':
4924     case KEY_NPAGE:
4925       // Page down key
4926       if (m_num_rows > m_max_y) {
4927         if (m_first_visible_row + m_max_y < m_num_rows) {
4928           m_first_visible_row += m_max_y;
4929           m_selected_row_idx = m_first_visible_row;
4930           m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4931           if (m_selected_item)
4932             m_selected_item->ItemWasSelected();
4933         }
4934       }
4935       return eKeyHandled;
4936 
4937     case KEY_UP:
4938       if (m_selected_row_idx > 0) {
4939         --m_selected_row_idx;
4940         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4941         if (m_selected_item)
4942           m_selected_item->ItemWasSelected();
4943       }
4944       return eKeyHandled;
4945 
4946     case KEY_DOWN:
4947       if (m_selected_row_idx + 1 < m_num_rows) {
4948         ++m_selected_row_idx;
4949         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4950         if (m_selected_item)
4951           m_selected_item->ItemWasSelected();
4952       }
4953       return eKeyHandled;
4954 
4955     case KEY_RIGHT:
4956       if (m_selected_item) {
4957         if (!m_selected_item->IsExpanded())
4958           m_selected_item->Expand();
4959       }
4960       return eKeyHandled;
4961 
4962     case KEY_LEFT:
4963       if (m_selected_item) {
4964         if (m_selected_item->IsExpanded())
4965           m_selected_item->Unexpand();
4966         else if (m_selected_item->GetParent()) {
4967           m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex();
4968           m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4969           if (m_selected_item)
4970             m_selected_item->ItemWasSelected();
4971         }
4972       }
4973       return eKeyHandled;
4974 
4975     case ' ':
4976       // Toggle expansion state when SPACE is pressed
4977       if (m_selected_item) {
4978         if (m_selected_item->IsExpanded())
4979           m_selected_item->Unexpand();
4980         else
4981           m_selected_item->Expand();
4982       }
4983       return eKeyHandled;
4984 
4985     case 'h':
4986       window.CreateHelpSubwindow();
4987       return eKeyHandled;
4988 
4989     default:
4990       break;
4991     }
4992     return eKeyNotHandled;
4993   }
4994 
4995 protected:
4996   Debugger &m_debugger;
4997   TreeDelegateSP m_delegate_sp;
4998   TreeItem m_root;
4999   TreeItem *m_selected_item;
5000   int m_num_rows;
5001   int m_selected_row_idx;
5002   int m_first_visible_row;
5003   int m_min_x;
5004   int m_min_y;
5005   int m_max_x;
5006   int m_max_y;
5007 };
5008 
5009 // A tree delegate that just draws the text member of the tree item, it doesn't
5010 // have any children or actions.
5011 class TextTreeDelegate : public TreeDelegate {
5012 public:
TextTreeDelegate()5013   TextTreeDelegate() : TreeDelegate() {}
5014 
5015   ~TextTreeDelegate() override = default;
5016 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5017   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5018     window.PutCStringTruncated(1, item.GetText().c_str());
5019   }
5020 
TreeDelegateGenerateChildren(TreeItem & item)5021   void TreeDelegateGenerateChildren(TreeItem &item) override {}
5022 
TreeDelegateItemSelected(TreeItem & item)5023   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5024 };
5025 
5026 class FrameTreeDelegate : public TreeDelegate {
5027 public:
FrameTreeDelegate()5028   FrameTreeDelegate() : TreeDelegate() {
5029     FormatEntity::Parse(
5030         "frame #${frame.index}: {${function.name}${function.pc-offset}}}",
5031         m_format);
5032   }
5033 
5034   ~FrameTreeDelegate() override = default;
5035 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5036   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5037     Thread *thread = (Thread *)item.GetUserData();
5038     if (thread) {
5039       const uint64_t frame_idx = item.GetIdentifier();
5040       StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx);
5041       if (frame_sp) {
5042         StreamString strm;
5043         const SymbolContext &sc =
5044             frame_sp->GetSymbolContext(eSymbolContextEverything);
5045         ExecutionContext exe_ctx(frame_sp);
5046         if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr,
5047                                  nullptr, false, false)) {
5048           int right_pad = 1;
5049           window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
5050         }
5051       }
5052     }
5053   }
5054 
TreeDelegateGenerateChildren(TreeItem & item)5055   void TreeDelegateGenerateChildren(TreeItem &item) override {
5056     // No children for frames yet...
5057   }
5058 
TreeDelegateItemSelected(TreeItem & item)5059   bool TreeDelegateItemSelected(TreeItem &item) override {
5060     Thread *thread = (Thread *)item.GetUserData();
5061     if (thread) {
5062       thread->GetProcess()->GetThreadList().SetSelectedThreadByID(
5063           thread->GetID());
5064       const uint64_t frame_idx = item.GetIdentifier();
5065       thread->SetSelectedFrameByIndex(frame_idx);
5066       return true;
5067     }
5068     return false;
5069   }
5070 
5071 protected:
5072   FormatEntity::Entry m_format;
5073 };
5074 
5075 class ThreadTreeDelegate : public TreeDelegate {
5076 public:
ThreadTreeDelegate(Debugger & debugger)5077   ThreadTreeDelegate(Debugger &debugger)
5078       : TreeDelegate(), m_debugger(debugger), m_tid(LLDB_INVALID_THREAD_ID),
5079         m_stop_id(UINT32_MAX) {
5080     FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop "
5081                         "reason = ${thread.stop-reason}}",
5082                         m_format);
5083   }
5084 
5085   ~ThreadTreeDelegate() override = default;
5086 
GetProcess()5087   ProcessSP GetProcess() {
5088     return m_debugger.GetCommandInterpreter()
5089         .GetExecutionContext()
5090         .GetProcessSP();
5091   }
5092 
GetThread(const TreeItem & item)5093   ThreadSP GetThread(const TreeItem &item) {
5094     ProcessSP process_sp = GetProcess();
5095     if (process_sp)
5096       return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier());
5097     return ThreadSP();
5098   }
5099 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5100   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5101     ThreadSP thread_sp = GetThread(item);
5102     if (thread_sp) {
5103       StreamString strm;
5104       ExecutionContext exe_ctx(thread_sp);
5105       if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
5106                                nullptr, false, false)) {
5107         int right_pad = 1;
5108         window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
5109       }
5110     }
5111   }
5112 
TreeDelegateGenerateChildren(TreeItem & item)5113   void TreeDelegateGenerateChildren(TreeItem &item) override {
5114     ProcessSP process_sp = GetProcess();
5115     if (process_sp && process_sp->IsAlive()) {
5116       StateType state = process_sp->GetState();
5117       if (StateIsStoppedState(state, true)) {
5118         ThreadSP thread_sp = GetThread(item);
5119         if (thread_sp) {
5120           if (m_stop_id == process_sp->GetStopID() &&
5121               thread_sp->GetID() == m_tid)
5122             return; // Children are already up to date
5123           if (!m_frame_delegate_sp) {
5124             // Always expand the thread item the first time we show it
5125             m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>();
5126           }
5127 
5128           m_stop_id = process_sp->GetStopID();
5129           m_tid = thread_sp->GetID();
5130 
5131           TreeItem t(&item, *m_frame_delegate_sp, false);
5132           size_t num_frames = thread_sp->GetStackFrameCount();
5133           item.Resize(num_frames, t);
5134           for (size_t i = 0; i < num_frames; ++i) {
5135             item[i].SetUserData(thread_sp.get());
5136             item[i].SetIdentifier(i);
5137           }
5138         }
5139         return;
5140       }
5141     }
5142     item.ClearChildren();
5143   }
5144 
TreeDelegateItemSelected(TreeItem & item)5145   bool TreeDelegateItemSelected(TreeItem &item) override {
5146     ProcessSP process_sp = GetProcess();
5147     if (process_sp && process_sp->IsAlive()) {
5148       StateType state = process_sp->GetState();
5149       if (StateIsStoppedState(state, true)) {
5150         ThreadSP thread_sp = GetThread(item);
5151         if (thread_sp) {
5152           ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList();
5153           std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex());
5154           ThreadSP selected_thread_sp = thread_list.GetSelectedThread();
5155           if (selected_thread_sp->GetID() != thread_sp->GetID()) {
5156             thread_list.SetSelectedThreadByID(thread_sp->GetID());
5157             return true;
5158           }
5159         }
5160       }
5161     }
5162     return false;
5163   }
5164 
5165 protected:
5166   Debugger &m_debugger;
5167   std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp;
5168   lldb::user_id_t m_tid;
5169   uint32_t m_stop_id;
5170   FormatEntity::Entry m_format;
5171 };
5172 
5173 class ThreadsTreeDelegate : public TreeDelegate {
5174 public:
ThreadsTreeDelegate(Debugger & debugger)5175   ThreadsTreeDelegate(Debugger &debugger)
5176       : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger),
5177         m_stop_id(UINT32_MAX), m_update_selection(false) {
5178     FormatEntity::Parse("process ${process.id}{, name = ${process.name}}",
5179                         m_format);
5180   }
5181 
5182   ~ThreadsTreeDelegate() override = default;
5183 
GetProcess()5184   ProcessSP GetProcess() {
5185     return m_debugger.GetCommandInterpreter()
5186         .GetExecutionContext()
5187         .GetProcessSP();
5188   }
5189 
TreeDelegateShouldDraw()5190   bool TreeDelegateShouldDraw() override {
5191     ProcessSP process = GetProcess();
5192     if (!process)
5193       return false;
5194 
5195     if (StateIsRunningState(process->GetState()))
5196       return false;
5197 
5198     return true;
5199   }
5200 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5201   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5202     ProcessSP process_sp = GetProcess();
5203     if (process_sp && process_sp->IsAlive()) {
5204       StreamString strm;
5205       ExecutionContext exe_ctx(process_sp);
5206       if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
5207                                nullptr, false, false)) {
5208         int right_pad = 1;
5209         window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
5210       }
5211     }
5212   }
5213 
TreeDelegateGenerateChildren(TreeItem & item)5214   void TreeDelegateGenerateChildren(TreeItem &item) override {
5215     ProcessSP process_sp = GetProcess();
5216     m_update_selection = false;
5217     if (process_sp && process_sp->IsAlive()) {
5218       StateType state = process_sp->GetState();
5219       if (StateIsStoppedState(state, true)) {
5220         const uint32_t stop_id = process_sp->GetStopID();
5221         if (m_stop_id == stop_id)
5222           return; // Children are already up to date
5223 
5224         m_stop_id = stop_id;
5225         m_update_selection = true;
5226 
5227         if (!m_thread_delegate_sp) {
5228           // Always expand the thread item the first time we show it
5229           // item.Expand();
5230           m_thread_delegate_sp =
5231               std::make_shared<ThreadTreeDelegate>(m_debugger);
5232         }
5233 
5234         TreeItem t(&item, *m_thread_delegate_sp, false);
5235         ThreadList &threads = process_sp->GetThreadList();
5236         std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
5237         ThreadSP selected_thread = threads.GetSelectedThread();
5238         size_t num_threads = threads.GetSize();
5239         item.Resize(num_threads, t);
5240         for (size_t i = 0; i < num_threads; ++i) {
5241           ThreadSP thread = threads.GetThreadAtIndex(i);
5242           item[i].SetIdentifier(thread->GetID());
5243           item[i].SetMightHaveChildren(true);
5244           if (selected_thread->GetID() == thread->GetID())
5245             item[i].Expand();
5246         }
5247         return;
5248       }
5249     }
5250     item.ClearChildren();
5251   }
5252 
TreeDelegateUpdateSelection(TreeItem & root,int & selection_index,TreeItem * & selected_item)5253   void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
5254                                    TreeItem *&selected_item) override {
5255     if (!m_update_selection)
5256       return;
5257 
5258     ProcessSP process_sp = GetProcess();
5259     if (!(process_sp && process_sp->IsAlive()))
5260       return;
5261 
5262     StateType state = process_sp->GetState();
5263     if (!StateIsStoppedState(state, true))
5264       return;
5265 
5266     ThreadList &threads = process_sp->GetThreadList();
5267     std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
5268     ThreadSP selected_thread = threads.GetSelectedThread();
5269     size_t num_threads = threads.GetSize();
5270     for (size_t i = 0; i < num_threads; ++i) {
5271       ThreadSP thread = threads.GetThreadAtIndex(i);
5272       if (selected_thread->GetID() == thread->GetID()) {
5273         selected_item = &root[i][thread->GetSelectedFrameIndex()];
5274         selection_index = selected_item->GetRowIndex();
5275         return;
5276       }
5277     }
5278   }
5279 
TreeDelegateItemSelected(TreeItem & item)5280   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5281 
TreeDelegateExpandRootByDefault()5282   bool TreeDelegateExpandRootByDefault() override { return true; }
5283 
5284 protected:
5285   std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp;
5286   Debugger &m_debugger;
5287   uint32_t m_stop_id;
5288   bool m_update_selection;
5289   FormatEntity::Entry m_format;
5290 };
5291 
5292 class BreakpointLocationTreeDelegate : public TreeDelegate {
5293 public:
BreakpointLocationTreeDelegate(Debugger & debugger)5294   BreakpointLocationTreeDelegate(Debugger &debugger)
5295       : TreeDelegate(), m_debugger(debugger) {}
5296 
5297   ~BreakpointLocationTreeDelegate() override = default;
5298 
GetProcess()5299   Process *GetProcess() {
5300     ExecutionContext exe_ctx(
5301         m_debugger.GetCommandInterpreter().GetExecutionContext());
5302     return exe_ctx.GetProcessPtr();
5303   }
5304 
GetBreakpointLocation(const TreeItem & item)5305   BreakpointLocationSP GetBreakpointLocation(const TreeItem &item) {
5306     Breakpoint *breakpoint = (Breakpoint *)item.GetUserData();
5307     return breakpoint->GetLocationAtIndex(item.GetIdentifier());
5308   }
5309 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5310   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5311     BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item);
5312     Process *process = GetProcess();
5313     StreamString stream;
5314     stream.Printf("%i.%i: ", breakpoint_location->GetBreakpoint().GetID(),
5315                   breakpoint_location->GetID());
5316     Address address = breakpoint_location->GetAddress();
5317     address.Dump(&stream, process, Address::DumpStyleResolvedDescription,
5318                  Address::DumpStyleInvalid);
5319     window.PutCStringTruncated(1, stream.GetString().str().c_str());
5320   }
5321 
ComputeDetailsList(BreakpointLocationSP breakpoint_location)5322   StringList ComputeDetailsList(BreakpointLocationSP breakpoint_location) {
5323     StringList details;
5324 
5325     Address address = breakpoint_location->GetAddress();
5326     SymbolContext symbol_context;
5327     address.CalculateSymbolContext(&symbol_context);
5328 
5329     if (symbol_context.module_sp) {
5330       StreamString module_stream;
5331       module_stream.PutCString("module = ");
5332       symbol_context.module_sp->GetFileSpec().Dump(
5333           module_stream.AsRawOstream());
5334       details.AppendString(module_stream.GetString());
5335     }
5336 
5337     if (symbol_context.comp_unit != nullptr) {
5338       StreamString compile_unit_stream;
5339       compile_unit_stream.PutCString("compile unit = ");
5340       symbol_context.comp_unit->GetPrimaryFile().GetFilename().Dump(
5341           &compile_unit_stream);
5342       details.AppendString(compile_unit_stream.GetString());
5343 
5344       if (symbol_context.function != nullptr) {
5345         StreamString function_stream;
5346         function_stream.PutCString("function = ");
5347         function_stream.PutCString(
5348             symbol_context.function->GetName().AsCString("<unknown>"));
5349         details.AppendString(function_stream.GetString());
5350       }
5351 
5352       if (symbol_context.line_entry.line > 0) {
5353         StreamString location_stream;
5354         location_stream.PutCString("location = ");
5355         symbol_context.line_entry.DumpStopContext(&location_stream, true);
5356         details.AppendString(location_stream.GetString());
5357       }
5358 
5359     } else {
5360       if (symbol_context.symbol) {
5361         StreamString symbol_stream;
5362         if (breakpoint_location->IsReExported())
5363           symbol_stream.PutCString("re-exported target = ");
5364         else
5365           symbol_stream.PutCString("symbol = ");
5366         symbol_stream.PutCString(
5367             symbol_context.symbol->GetName().AsCString("<unknown>"));
5368         details.AppendString(symbol_stream.GetString());
5369       }
5370     }
5371 
5372     Process *process = GetProcess();
5373 
5374     StreamString address_stream;
5375     address.Dump(&address_stream, process, Address::DumpStyleLoadAddress,
5376                  Address::DumpStyleModuleWithFileAddress);
5377     details.AppendString(address_stream.GetString());
5378 
5379     BreakpointSiteSP breakpoint_site = breakpoint_location->GetBreakpointSite();
5380     if (breakpoint_location->IsIndirect() && breakpoint_site) {
5381       Address resolved_address;
5382       resolved_address.SetLoadAddress(breakpoint_site->GetLoadAddress(),
5383                                       &breakpoint_location->GetTarget());
5384       Symbol *resolved_symbol = resolved_address.CalculateSymbolContextSymbol();
5385       if (resolved_symbol) {
5386         StreamString indirect_target_stream;
5387         indirect_target_stream.PutCString("indirect target = ");
5388         indirect_target_stream.PutCString(
5389             resolved_symbol->GetName().GetCString());
5390         details.AppendString(indirect_target_stream.GetString());
5391       }
5392     }
5393 
5394     bool is_resolved = breakpoint_location->IsResolved();
5395     StreamString resolved_stream;
5396     resolved_stream.Printf("resolved = %s", is_resolved ? "true" : "false");
5397     details.AppendString(resolved_stream.GetString());
5398 
5399     bool is_hardware = is_resolved && breakpoint_site->IsHardware();
5400     StreamString hardware_stream;
5401     hardware_stream.Printf("hardware = %s", is_hardware ? "true" : "false");
5402     details.AppendString(hardware_stream.GetString());
5403 
5404     StreamString hit_count_stream;
5405     hit_count_stream.Printf("hit count = %-4u",
5406                             breakpoint_location->GetHitCount());
5407     details.AppendString(hit_count_stream.GetString());
5408 
5409     return details;
5410   }
5411 
TreeDelegateGenerateChildren(TreeItem & item)5412   void TreeDelegateGenerateChildren(TreeItem &item) override {
5413     BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item);
5414     StringList details = ComputeDetailsList(breakpoint_location);
5415 
5416     if (!m_string_delegate_sp)
5417       m_string_delegate_sp = std::make_shared<TextTreeDelegate>();
5418     TreeItem details_tree_item(&item, *m_string_delegate_sp, false);
5419 
5420     item.Resize(details.GetSize(), details_tree_item);
5421     for (size_t i = 0; i < details.GetSize(); i++) {
5422       item[i].SetText(details.GetStringAtIndex(i));
5423     }
5424   }
5425 
TreeDelegateItemSelected(TreeItem & item)5426   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5427 
5428 protected:
5429   Debugger &m_debugger;
5430   std::shared_ptr<TextTreeDelegate> m_string_delegate_sp;
5431 };
5432 
5433 class BreakpointTreeDelegate : public TreeDelegate {
5434 public:
BreakpointTreeDelegate(Debugger & debugger)5435   BreakpointTreeDelegate(Debugger &debugger)
5436       : TreeDelegate(), m_debugger(debugger),
5437         m_breakpoint_location_delegate_sp() {}
5438 
5439   ~BreakpointTreeDelegate() override = default;
5440 
GetBreakpoint(const TreeItem & item)5441   BreakpointSP GetBreakpoint(const TreeItem &item) {
5442     TargetSP target = m_debugger.GetSelectedTarget();
5443     BreakpointList &breakpoints = target->GetBreakpointList(false);
5444     return breakpoints.GetBreakpointAtIndex(item.GetIdentifier());
5445   }
5446 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5447   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5448     BreakpointSP breakpoint = GetBreakpoint(item);
5449     StreamString stream;
5450     stream.Format("{0}: ", breakpoint->GetID());
5451     breakpoint->GetResolverDescription(&stream);
5452     breakpoint->GetFilterDescription(&stream);
5453     window.PutCStringTruncated(1, stream.GetString().str().c_str());
5454   }
5455 
TreeDelegateGenerateChildren(TreeItem & item)5456   void TreeDelegateGenerateChildren(TreeItem &item) override {
5457     BreakpointSP breakpoint = GetBreakpoint(item);
5458 
5459     if (!m_breakpoint_location_delegate_sp)
5460       m_breakpoint_location_delegate_sp =
5461           std::make_shared<BreakpointLocationTreeDelegate>(m_debugger);
5462     TreeItem breakpoint_location_tree_item(
5463         &item, *m_breakpoint_location_delegate_sp, true);
5464 
5465     item.Resize(breakpoint->GetNumLocations(), breakpoint_location_tree_item);
5466     for (size_t i = 0; i < breakpoint->GetNumLocations(); i++) {
5467       item[i].SetIdentifier(i);
5468       item[i].SetUserData(breakpoint.get());
5469     }
5470   }
5471 
TreeDelegateItemSelected(TreeItem & item)5472   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5473 
5474 protected:
5475   Debugger &m_debugger;
5476   std::shared_ptr<BreakpointLocationTreeDelegate>
5477       m_breakpoint_location_delegate_sp;
5478 };
5479 
5480 class BreakpointsTreeDelegate : public TreeDelegate {
5481 public:
BreakpointsTreeDelegate(Debugger & debugger)5482   BreakpointsTreeDelegate(Debugger &debugger)
5483       : TreeDelegate(), m_debugger(debugger), m_breakpoint_delegate_sp() {}
5484 
5485   ~BreakpointsTreeDelegate() override = default;
5486 
TreeDelegateShouldDraw()5487   bool TreeDelegateShouldDraw() override {
5488     TargetSP target = m_debugger.GetSelectedTarget();
5489     if (!target)
5490       return false;
5491 
5492     return true;
5493   }
5494 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5495   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5496     window.PutCString("Breakpoints");
5497   }
5498 
TreeDelegateGenerateChildren(TreeItem & item)5499   void TreeDelegateGenerateChildren(TreeItem &item) override {
5500     TargetSP target = m_debugger.GetSelectedTarget();
5501 
5502     BreakpointList &breakpoints = target->GetBreakpointList(false);
5503     std::unique_lock<std::recursive_mutex> lock;
5504     breakpoints.GetListMutex(lock);
5505 
5506     if (!m_breakpoint_delegate_sp)
5507       m_breakpoint_delegate_sp =
5508           std::make_shared<BreakpointTreeDelegate>(m_debugger);
5509     TreeItem breakpoint_tree_item(&item, *m_breakpoint_delegate_sp, true);
5510 
5511     item.Resize(breakpoints.GetSize(), breakpoint_tree_item);
5512     for (size_t i = 0; i < breakpoints.GetSize(); i++) {
5513       item[i].SetIdentifier(i);
5514     }
5515   }
5516 
TreeDelegateItemSelected(TreeItem & item)5517   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5518 
TreeDelegateExpandRootByDefault()5519   bool TreeDelegateExpandRootByDefault() override { return true; }
5520 
5521 protected:
5522   Debugger &m_debugger;
5523   std::shared_ptr<BreakpointTreeDelegate> m_breakpoint_delegate_sp;
5524 };
5525 
5526 class ValueObjectListDelegate : public WindowDelegate {
5527 public:
ValueObjectListDelegate()5528   ValueObjectListDelegate() : m_rows() {}
5529 
ValueObjectListDelegate(ValueObjectList & valobj_list)5530   ValueObjectListDelegate(ValueObjectList &valobj_list)
5531       : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0),
5532         m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) {
5533     SetValues(valobj_list);
5534   }
5535 
5536   ~ValueObjectListDelegate() override = default;
5537 
SetValues(ValueObjectList & valobj_list)5538   void SetValues(ValueObjectList &valobj_list) {
5539     m_selected_row = nullptr;
5540     m_selected_row_idx = 0;
5541     m_first_visible_row = 0;
5542     m_num_rows = 0;
5543     m_rows.clear();
5544     for (auto &valobj_sp : valobj_list.GetObjects())
5545       m_rows.push_back(Row(valobj_sp, nullptr));
5546   }
5547 
WindowDelegateDraw(Window & window,bool force)5548   bool WindowDelegateDraw(Window &window, bool force) override {
5549     m_num_rows = 0;
5550     m_min_x = 2;
5551     m_min_y = 1;
5552     m_max_x = window.GetWidth() - 1;
5553     m_max_y = window.GetHeight() - 1;
5554 
5555     window.Erase();
5556     window.DrawTitleBox(window.GetName());
5557 
5558     const int num_visible_rows = NumVisibleRows();
5559     const int num_rows = CalculateTotalNumberRows(m_rows);
5560 
5561     // If we unexpanded while having something selected our total number of
5562     // rows is less than the num visible rows, then make sure we show all the
5563     // rows by setting the first visible row accordingly.
5564     if (m_first_visible_row > 0 && num_rows < num_visible_rows)
5565       m_first_visible_row = 0;
5566 
5567     // Make sure the selected row is always visible
5568     if (m_selected_row_idx < m_first_visible_row)
5569       m_first_visible_row = m_selected_row_idx;
5570     else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
5571       m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
5572 
5573     DisplayRows(window, m_rows, g_options);
5574 
5575     // Get the selected row
5576     m_selected_row = GetRowForRowIndex(m_selected_row_idx);
5577     // Keep the cursor on the selected row so the highlight and the cursor are
5578     // always on the same line
5579     if (m_selected_row)
5580       window.MoveCursor(m_selected_row->x, m_selected_row->y);
5581 
5582     return true; // Drawing handled
5583   }
5584 
WindowDelegateGetKeyHelp()5585   KeyHelp *WindowDelegateGetKeyHelp() override {
5586     static curses::KeyHelp g_source_view_key_help[] = {
5587         {KEY_UP, "Select previous item"},
5588         {KEY_DOWN, "Select next item"},
5589         {KEY_RIGHT, "Expand selected item"},
5590         {KEY_LEFT, "Unexpand selected item or select parent if not expanded"},
5591         {KEY_PPAGE, "Page up"},
5592         {KEY_NPAGE, "Page down"},
5593         {'A', "Format as annotated address"},
5594         {'b', "Format as binary"},
5595         {'B', "Format as hex bytes with ASCII"},
5596         {'c', "Format as character"},
5597         {'d', "Format as a signed integer"},
5598         {'D', "Format selected value using the default format for the type"},
5599         {'f', "Format as float"},
5600         {'h', "Show help dialog"},
5601         {'i', "Format as instructions"},
5602         {'o', "Format as octal"},
5603         {'p', "Format as pointer"},
5604         {'s', "Format as C string"},
5605         {'t', "Toggle showing/hiding type names"},
5606         {'u', "Format as an unsigned integer"},
5607         {'x', "Format as hex"},
5608         {'X', "Format as uppercase hex"},
5609         {' ', "Toggle item expansion"},
5610         {',', "Page up"},
5611         {'.', "Page down"},
5612         {'\0', nullptr}};
5613     return g_source_view_key_help;
5614   }
5615 
WindowDelegateHandleChar(Window & window,int c)5616   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
5617     switch (c) {
5618     case 'x':
5619     case 'X':
5620     case 'o':
5621     case 's':
5622     case 'u':
5623     case 'd':
5624     case 'D':
5625     case 'i':
5626     case 'A':
5627     case 'p':
5628     case 'c':
5629     case 'b':
5630     case 'B':
5631     case 'f':
5632       // Change the format for the currently selected item
5633       if (m_selected_row) {
5634         auto valobj_sp = m_selected_row->value.GetSP();
5635         if (valobj_sp)
5636           valobj_sp->SetFormat(FormatForChar(c));
5637       }
5638       return eKeyHandled;
5639 
5640     case 't':
5641       // Toggle showing type names
5642       g_options.show_types = !g_options.show_types;
5643       return eKeyHandled;
5644 
5645     case ',':
5646     case KEY_PPAGE:
5647       // Page up key
5648       if (m_first_visible_row > 0) {
5649         if (static_cast<int>(m_first_visible_row) > m_max_y)
5650           m_first_visible_row -= m_max_y;
5651         else
5652           m_first_visible_row = 0;
5653         m_selected_row_idx = m_first_visible_row;
5654       }
5655       return eKeyHandled;
5656 
5657     case '.':
5658     case KEY_NPAGE:
5659       // Page down key
5660       if (m_num_rows > static_cast<size_t>(m_max_y)) {
5661         if (m_first_visible_row + m_max_y < m_num_rows) {
5662           m_first_visible_row += m_max_y;
5663           m_selected_row_idx = m_first_visible_row;
5664         }
5665       }
5666       return eKeyHandled;
5667 
5668     case KEY_UP:
5669       if (m_selected_row_idx > 0)
5670         --m_selected_row_idx;
5671       return eKeyHandled;
5672 
5673     case KEY_DOWN:
5674       if (m_selected_row_idx + 1 < m_num_rows)
5675         ++m_selected_row_idx;
5676       return eKeyHandled;
5677 
5678     case KEY_RIGHT:
5679       if (m_selected_row) {
5680         if (!m_selected_row->expanded)
5681           m_selected_row->Expand();
5682       }
5683       return eKeyHandled;
5684 
5685     case KEY_LEFT:
5686       if (m_selected_row) {
5687         if (m_selected_row->expanded)
5688           m_selected_row->Unexpand();
5689         else if (m_selected_row->parent)
5690           m_selected_row_idx = m_selected_row->parent->row_idx;
5691       }
5692       return eKeyHandled;
5693 
5694     case ' ':
5695       // Toggle expansion state when SPACE is pressed
5696       if (m_selected_row) {
5697         if (m_selected_row->expanded)
5698           m_selected_row->Unexpand();
5699         else
5700           m_selected_row->Expand();
5701       }
5702       return eKeyHandled;
5703 
5704     case 'h':
5705       window.CreateHelpSubwindow();
5706       return eKeyHandled;
5707 
5708     default:
5709       break;
5710     }
5711     return eKeyNotHandled;
5712   }
5713 
5714 protected:
5715   std::vector<Row> m_rows;
5716   Row *m_selected_row = nullptr;
5717   uint32_t m_selected_row_idx = 0;
5718   uint32_t m_first_visible_row = 0;
5719   uint32_t m_num_rows = 0;
5720   int m_min_x;
5721   int m_min_y;
5722   int m_max_x = 0;
5723   int m_max_y = 0;
5724 
FormatForChar(int c)5725   static Format FormatForChar(int c) {
5726     switch (c) {
5727     case 'x':
5728       return eFormatHex;
5729     case 'X':
5730       return eFormatHexUppercase;
5731     case 'o':
5732       return eFormatOctal;
5733     case 's':
5734       return eFormatCString;
5735     case 'u':
5736       return eFormatUnsigned;
5737     case 'd':
5738       return eFormatDecimal;
5739     case 'D':
5740       return eFormatDefault;
5741     case 'i':
5742       return eFormatInstruction;
5743     case 'A':
5744       return eFormatAddressInfo;
5745     case 'p':
5746       return eFormatPointer;
5747     case 'c':
5748       return eFormatChar;
5749     case 'b':
5750       return eFormatBinary;
5751     case 'B':
5752       return eFormatBytesWithASCII;
5753     case 'f':
5754       return eFormatFloat;
5755     }
5756     return eFormatDefault;
5757   }
5758 
DisplayRowObject(Window & window,Row & row,DisplayOptions & options,bool highlight,bool last_child)5759   bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options,
5760                         bool highlight, bool last_child) {
5761     ValueObject *valobj = row.value.GetSP().get();
5762 
5763     if (valobj == nullptr)
5764       return false;
5765 
5766     const char *type_name =
5767         options.show_types ? valobj->GetTypeName().GetCString() : nullptr;
5768     const char *name = valobj->GetName().GetCString();
5769     const char *value = valobj->GetValueAsCString();
5770     const char *summary = valobj->GetSummaryAsCString();
5771 
5772     window.MoveCursor(row.x, row.y);
5773 
5774     row.DrawTree(window);
5775 
5776     if (highlight)
5777       window.AttributeOn(A_REVERSE);
5778 
5779     if (type_name && type_name[0])
5780       window.PrintfTruncated(1, "(%s) ", type_name);
5781 
5782     if (name && name[0])
5783       window.PutCStringTruncated(1, name);
5784 
5785     attr_t changd_attr = 0;
5786     if (valobj->GetValueDidChange())
5787       changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD;
5788 
5789     if (value && value[0]) {
5790       window.PutCStringTruncated(1, " = ");
5791       if (changd_attr)
5792         window.AttributeOn(changd_attr);
5793       window.PutCStringTruncated(1, value);
5794       if (changd_attr)
5795         window.AttributeOff(changd_attr);
5796     }
5797 
5798     if (summary && summary[0]) {
5799       window.PutCStringTruncated(1, " ");
5800       if (changd_attr)
5801         window.AttributeOn(changd_attr);
5802       window.PutCStringTruncated(1, summary);
5803       if (changd_attr)
5804         window.AttributeOff(changd_attr);
5805     }
5806 
5807     if (highlight)
5808       window.AttributeOff(A_REVERSE);
5809 
5810     return true;
5811   }
5812 
DisplayRows(Window & window,std::vector<Row> & rows,DisplayOptions & options)5813   void DisplayRows(Window &window, std::vector<Row> &rows,
5814                    DisplayOptions &options) {
5815     // >   0x25B7
5816     // \/  0x25BD
5817 
5818     bool window_is_active = window.IsActive();
5819     for (auto &row : rows) {
5820       const bool last_child = row.parent && &rows[rows.size() - 1] == &row;
5821       // Save the row index in each Row structure
5822       row.row_idx = m_num_rows;
5823       if ((m_num_rows >= m_first_visible_row) &&
5824           ((m_num_rows - m_first_visible_row) <
5825            static_cast<size_t>(NumVisibleRows()))) {
5826         row.x = m_min_x;
5827         row.y = m_num_rows - m_first_visible_row + 1;
5828         if (DisplayRowObject(window, row, options,
5829                              window_is_active &&
5830                                  m_num_rows == m_selected_row_idx,
5831                              last_child)) {
5832           ++m_num_rows;
5833         } else {
5834           row.x = 0;
5835           row.y = 0;
5836         }
5837       } else {
5838         row.x = 0;
5839         row.y = 0;
5840         ++m_num_rows;
5841       }
5842 
5843       auto &children = row.GetChildren();
5844       if (row.expanded && !children.empty()) {
5845         DisplayRows(window, children, options);
5846       }
5847     }
5848   }
5849 
CalculateTotalNumberRows(std::vector<Row> & rows)5850   int CalculateTotalNumberRows(std::vector<Row> &rows) {
5851     int row_count = 0;
5852     for (auto &row : rows) {
5853       ++row_count;
5854       if (row.expanded)
5855         row_count += CalculateTotalNumberRows(row.GetChildren());
5856     }
5857     return row_count;
5858   }
5859 
GetRowForRowIndexImpl(std::vector<Row> & rows,size_t & row_index)5860   static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) {
5861     for (auto &row : rows) {
5862       if (row_index == 0)
5863         return &row;
5864       else {
5865         --row_index;
5866         auto &children = row.GetChildren();
5867         if (row.expanded && !children.empty()) {
5868           Row *result = GetRowForRowIndexImpl(children, row_index);
5869           if (result)
5870             return result;
5871         }
5872       }
5873     }
5874     return nullptr;
5875   }
5876 
GetRowForRowIndex(size_t row_index)5877   Row *GetRowForRowIndex(size_t row_index) {
5878     return GetRowForRowIndexImpl(m_rows, row_index);
5879   }
5880 
NumVisibleRows() const5881   int NumVisibleRows() const { return m_max_y - m_min_y; }
5882 
5883   static DisplayOptions g_options;
5884 };
5885 
5886 class FrameVariablesWindowDelegate : public ValueObjectListDelegate {
5887 public:
FrameVariablesWindowDelegate(Debugger & debugger)5888   FrameVariablesWindowDelegate(Debugger &debugger)
5889       : ValueObjectListDelegate(), m_debugger(debugger),
5890         m_frame_block(nullptr) {}
5891 
5892   ~FrameVariablesWindowDelegate() override = default;
5893 
WindowDelegateGetHelpText()5894   const char *WindowDelegateGetHelpText() override {
5895     return "Frame variable window keyboard shortcuts:";
5896   }
5897 
WindowDelegateDraw(Window & window,bool force)5898   bool WindowDelegateDraw(Window &window, bool force) override {
5899     ExecutionContext exe_ctx(
5900         m_debugger.GetCommandInterpreter().GetExecutionContext());
5901     Process *process = exe_ctx.GetProcessPtr();
5902     Block *frame_block = nullptr;
5903     StackFrame *frame = nullptr;
5904 
5905     if (process) {
5906       StateType state = process->GetState();
5907       if (StateIsStoppedState(state, true)) {
5908         frame = exe_ctx.GetFramePtr();
5909         if (frame)
5910           frame_block = frame->GetFrameBlock();
5911       } else if (StateIsRunningState(state)) {
5912         return true; // Don't do any updating when we are running
5913       }
5914     }
5915 
5916     ValueObjectList local_values;
5917     if (frame_block) {
5918       // Only update the variables if they have changed
5919       if (m_frame_block != frame_block) {
5920         m_frame_block = frame_block;
5921 
5922         VariableList *locals = frame->GetVariableList(true);
5923         if (locals) {
5924           const DynamicValueType use_dynamic = eDynamicDontRunTarget;
5925           for (const VariableSP &local_sp : *locals) {
5926             ValueObjectSP value_sp =
5927                 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic);
5928             if (value_sp) {
5929               ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue();
5930               if (synthetic_value_sp)
5931                 local_values.Append(synthetic_value_sp);
5932               else
5933                 local_values.Append(value_sp);
5934             }
5935           }
5936           // Update the values
5937           SetValues(local_values);
5938         }
5939       }
5940     } else {
5941       m_frame_block = nullptr;
5942       // Update the values with an empty list if there is no frame
5943       SetValues(local_values);
5944     }
5945 
5946     return ValueObjectListDelegate::WindowDelegateDraw(window, force);
5947   }
5948 
5949 protected:
5950   Debugger &m_debugger;
5951   Block *m_frame_block;
5952 };
5953 
5954 class RegistersWindowDelegate : public ValueObjectListDelegate {
5955 public:
RegistersWindowDelegate(Debugger & debugger)5956   RegistersWindowDelegate(Debugger &debugger)
5957       : ValueObjectListDelegate(), m_debugger(debugger) {}
5958 
5959   ~RegistersWindowDelegate() override = default;
5960 
WindowDelegateGetHelpText()5961   const char *WindowDelegateGetHelpText() override {
5962     return "Register window keyboard shortcuts:";
5963   }
5964 
WindowDelegateDraw(Window & window,bool force)5965   bool WindowDelegateDraw(Window &window, bool force) override {
5966     ExecutionContext exe_ctx(
5967         m_debugger.GetCommandInterpreter().GetExecutionContext());
5968     StackFrame *frame = exe_ctx.GetFramePtr();
5969 
5970     ValueObjectList value_list;
5971     if (frame) {
5972       if (frame->GetStackID() != m_stack_id) {
5973         m_stack_id = frame->GetStackID();
5974         RegisterContextSP reg_ctx(frame->GetRegisterContext());
5975         if (reg_ctx) {
5976           const uint32_t num_sets = reg_ctx->GetRegisterSetCount();
5977           for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) {
5978             value_list.Append(
5979                 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx));
5980           }
5981         }
5982         SetValues(value_list);
5983       }
5984     } else {
5985       Process *process = exe_ctx.GetProcessPtr();
5986       if (process && process->IsAlive())
5987         return true; // Don't do any updating if we are running
5988       else {
5989         // Update the values with an empty list if there is no process or the
5990         // process isn't alive anymore
5991         SetValues(value_list);
5992       }
5993     }
5994     return ValueObjectListDelegate::WindowDelegateDraw(window, force);
5995   }
5996 
5997 protected:
5998   Debugger &m_debugger;
5999   StackID m_stack_id;
6000 };
6001 
CursesKeyToCString(int ch)6002 static const char *CursesKeyToCString(int ch) {
6003   static char g_desc[32];
6004   if (ch >= KEY_F0 && ch < KEY_F0 + 64) {
6005     snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0);
6006     return g_desc;
6007   }
6008   switch (ch) {
6009   case KEY_DOWN:
6010     return "down";
6011   case KEY_UP:
6012     return "up";
6013   case KEY_LEFT:
6014     return "left";
6015   case KEY_RIGHT:
6016     return "right";
6017   case KEY_HOME:
6018     return "home";
6019   case KEY_BACKSPACE:
6020     return "backspace";
6021   case KEY_DL:
6022     return "delete-line";
6023   case KEY_IL:
6024     return "insert-line";
6025   case KEY_DC:
6026     return "delete-char";
6027   case KEY_IC:
6028     return "insert-char";
6029   case KEY_CLEAR:
6030     return "clear";
6031   case KEY_EOS:
6032     return "clear-to-eos";
6033   case KEY_EOL:
6034     return "clear-to-eol";
6035   case KEY_SF:
6036     return "scroll-forward";
6037   case KEY_SR:
6038     return "scroll-backward";
6039   case KEY_NPAGE:
6040     return "page-down";
6041   case KEY_PPAGE:
6042     return "page-up";
6043   case KEY_STAB:
6044     return "set-tab";
6045   case KEY_CTAB:
6046     return "clear-tab";
6047   case KEY_CATAB:
6048     return "clear-all-tabs";
6049   case KEY_ENTER:
6050     return "enter";
6051   case KEY_PRINT:
6052     return "print";
6053   case KEY_LL:
6054     return "lower-left key";
6055   case KEY_A1:
6056     return "upper left of keypad";
6057   case KEY_A3:
6058     return "upper right of keypad";
6059   case KEY_B2:
6060     return "center of keypad";
6061   case KEY_C1:
6062     return "lower left of keypad";
6063   case KEY_C3:
6064     return "lower right of keypad";
6065   case KEY_BTAB:
6066     return "back-tab key";
6067   case KEY_BEG:
6068     return "begin key";
6069   case KEY_CANCEL:
6070     return "cancel key";
6071   case KEY_CLOSE:
6072     return "close key";
6073   case KEY_COMMAND:
6074     return "command key";
6075   case KEY_COPY:
6076     return "copy key";
6077   case KEY_CREATE:
6078     return "create key";
6079   case KEY_END:
6080     return "end key";
6081   case KEY_EXIT:
6082     return "exit key";
6083   case KEY_FIND:
6084     return "find key";
6085   case KEY_HELP:
6086     return "help key";
6087   case KEY_MARK:
6088     return "mark key";
6089   case KEY_MESSAGE:
6090     return "message key";
6091   case KEY_MOVE:
6092     return "move key";
6093   case KEY_NEXT:
6094     return "next key";
6095   case KEY_OPEN:
6096     return "open key";
6097   case KEY_OPTIONS:
6098     return "options key";
6099   case KEY_PREVIOUS:
6100     return "previous key";
6101   case KEY_REDO:
6102     return "redo key";
6103   case KEY_REFERENCE:
6104     return "reference key";
6105   case KEY_REFRESH:
6106     return "refresh key";
6107   case KEY_REPLACE:
6108     return "replace key";
6109   case KEY_RESTART:
6110     return "restart key";
6111   case KEY_RESUME:
6112     return "resume key";
6113   case KEY_SAVE:
6114     return "save key";
6115   case KEY_SBEG:
6116     return "shifted begin key";
6117   case KEY_SCANCEL:
6118     return "shifted cancel key";
6119   case KEY_SCOMMAND:
6120     return "shifted command key";
6121   case KEY_SCOPY:
6122     return "shifted copy key";
6123   case KEY_SCREATE:
6124     return "shifted create key";
6125   case KEY_SDC:
6126     return "shifted delete-character key";
6127   case KEY_SDL:
6128     return "shifted delete-line key";
6129   case KEY_SELECT:
6130     return "select key";
6131   case KEY_SEND:
6132     return "shifted end key";
6133   case KEY_SEOL:
6134     return "shifted clear-to-end-of-line key";
6135   case KEY_SEXIT:
6136     return "shifted exit key";
6137   case KEY_SFIND:
6138     return "shifted find key";
6139   case KEY_SHELP:
6140     return "shifted help key";
6141   case KEY_SHOME:
6142     return "shifted home key";
6143   case KEY_SIC:
6144     return "shifted insert-character key";
6145   case KEY_SLEFT:
6146     return "shifted left-arrow key";
6147   case KEY_SMESSAGE:
6148     return "shifted message key";
6149   case KEY_SMOVE:
6150     return "shifted move key";
6151   case KEY_SNEXT:
6152     return "shifted next key";
6153   case KEY_SOPTIONS:
6154     return "shifted options key";
6155   case KEY_SPREVIOUS:
6156     return "shifted previous key";
6157   case KEY_SPRINT:
6158     return "shifted print key";
6159   case KEY_SREDO:
6160     return "shifted redo key";
6161   case KEY_SREPLACE:
6162     return "shifted replace key";
6163   case KEY_SRIGHT:
6164     return "shifted right-arrow key";
6165   case KEY_SRSUME:
6166     return "shifted resume key";
6167   case KEY_SSAVE:
6168     return "shifted save key";
6169   case KEY_SSUSPEND:
6170     return "shifted suspend key";
6171   case KEY_SUNDO:
6172     return "shifted undo key";
6173   case KEY_SUSPEND:
6174     return "suspend key";
6175   case KEY_UNDO:
6176     return "undo key";
6177   case KEY_MOUSE:
6178     return "Mouse event has occurred";
6179   case KEY_RESIZE:
6180     return "Terminal resize event";
6181 #ifdef KEY_EVENT
6182   case KEY_EVENT:
6183     return "We were interrupted by an event";
6184 #endif
6185   case KEY_RETURN:
6186     return "return";
6187   case ' ':
6188     return "space";
6189   case '\t':
6190     return "tab";
6191   case KEY_ESCAPE:
6192     return "escape";
6193   default:
6194     if (llvm::isPrint(ch))
6195       snprintf(g_desc, sizeof(g_desc), "%c", ch);
6196     else
6197       snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch);
6198     return g_desc;
6199   }
6200   return nullptr;
6201 }
6202 
HelpDialogDelegate(const char * text,KeyHelp * key_help_array)6203 HelpDialogDelegate::HelpDialogDelegate(const char *text,
6204                                        KeyHelp *key_help_array)
6205     : m_text(), m_first_visible_line(0) {
6206   if (text && text[0]) {
6207     m_text.SplitIntoLines(text);
6208     m_text.AppendString("");
6209   }
6210   if (key_help_array) {
6211     for (KeyHelp *key = key_help_array; key->ch; ++key) {
6212       StreamString key_description;
6213       key_description.Printf("%10s - %s", CursesKeyToCString(key->ch),
6214                              key->description);
6215       m_text.AppendString(key_description.GetString());
6216     }
6217   }
6218 }
6219 
6220 HelpDialogDelegate::~HelpDialogDelegate() = default;
6221 
WindowDelegateDraw(Window & window,bool force)6222 bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) {
6223   window.Erase();
6224   const int window_height = window.GetHeight();
6225   int x = 2;
6226   int y = 1;
6227   const int min_y = y;
6228   const int max_y = window_height - 1 - y;
6229   const size_t num_visible_lines = max_y - min_y + 1;
6230   const size_t num_lines = m_text.GetSize();
6231   const char *bottom_message;
6232   if (num_lines <= num_visible_lines)
6233     bottom_message = "Press any key to exit";
6234   else
6235     bottom_message = "Use arrows to scroll, any other key to exit";
6236   window.DrawTitleBox(window.GetName(), bottom_message);
6237   while (y <= max_y) {
6238     window.MoveCursor(x, y);
6239     window.PutCStringTruncated(
6240         1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y));
6241     ++y;
6242   }
6243   return true;
6244 }
6245 
WindowDelegateHandleChar(Window & window,int key)6246 HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window,
6247                                                               int key) {
6248   bool done = false;
6249   const size_t num_lines = m_text.GetSize();
6250   const size_t num_visible_lines = window.GetHeight() - 2;
6251 
6252   if (num_lines <= num_visible_lines) {
6253     done = true;
6254     // If we have all lines visible and don't need scrolling, then any key
6255     // press will cause us to exit
6256   } else {
6257     switch (key) {
6258     case KEY_UP:
6259       if (m_first_visible_line > 0)
6260         --m_first_visible_line;
6261       break;
6262 
6263     case KEY_DOWN:
6264       if (m_first_visible_line + num_visible_lines < num_lines)
6265         ++m_first_visible_line;
6266       break;
6267 
6268     case KEY_PPAGE:
6269     case ',':
6270       if (m_first_visible_line > 0) {
6271         if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines)
6272           m_first_visible_line -= num_visible_lines;
6273         else
6274           m_first_visible_line = 0;
6275       }
6276       break;
6277 
6278     case KEY_NPAGE:
6279     case '.':
6280       if (m_first_visible_line + num_visible_lines < num_lines) {
6281         m_first_visible_line += num_visible_lines;
6282         if (static_cast<size_t>(m_first_visible_line) > num_lines)
6283           m_first_visible_line = num_lines - num_visible_lines;
6284       }
6285       break;
6286 
6287     default:
6288       done = true;
6289       break;
6290     }
6291   }
6292   if (done)
6293     window.GetParent()->RemoveSubWindow(&window);
6294   return eKeyHandled;
6295 }
6296 
6297 class ApplicationDelegate : public WindowDelegate, public MenuDelegate {
6298 public:
6299   enum {
6300     eMenuID_LLDB = 1,
6301     eMenuID_LLDBAbout,
6302     eMenuID_LLDBExit,
6303 
6304     eMenuID_Target,
6305     eMenuID_TargetCreate,
6306     eMenuID_TargetDelete,
6307 
6308     eMenuID_Process,
6309     eMenuID_ProcessAttach,
6310     eMenuID_ProcessDetachResume,
6311     eMenuID_ProcessDetachSuspended,
6312     eMenuID_ProcessLaunch,
6313     eMenuID_ProcessContinue,
6314     eMenuID_ProcessHalt,
6315     eMenuID_ProcessKill,
6316 
6317     eMenuID_Thread,
6318     eMenuID_ThreadStepIn,
6319     eMenuID_ThreadStepOver,
6320     eMenuID_ThreadStepOut,
6321 
6322     eMenuID_View,
6323     eMenuID_ViewBacktrace,
6324     eMenuID_ViewRegisters,
6325     eMenuID_ViewSource,
6326     eMenuID_ViewVariables,
6327     eMenuID_ViewBreakpoints,
6328 
6329     eMenuID_Help,
6330     eMenuID_HelpGUIHelp
6331   };
6332 
ApplicationDelegate(Application & app,Debugger & debugger)6333   ApplicationDelegate(Application &app, Debugger &debugger)
6334       : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {}
6335 
6336   ~ApplicationDelegate() override = default;
6337 
WindowDelegateDraw(Window & window,bool force)6338   bool WindowDelegateDraw(Window &window, bool force) override {
6339     return false; // Drawing not handled, let standard window drawing happen
6340   }
6341 
WindowDelegateHandleChar(Window & window,int key)6342   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
6343     switch (key) {
6344     case '\t':
6345       window.SelectNextWindowAsActive();
6346       return eKeyHandled;
6347 
6348     case KEY_SHIFT_TAB:
6349       window.SelectPreviousWindowAsActive();
6350       return eKeyHandled;
6351 
6352     case 'h':
6353       window.CreateHelpSubwindow();
6354       return eKeyHandled;
6355 
6356     case KEY_ESCAPE:
6357       return eQuitApplication;
6358 
6359     default:
6360       break;
6361     }
6362     return eKeyNotHandled;
6363   }
6364 
WindowDelegateGetHelpText()6365   const char *WindowDelegateGetHelpText() override {
6366     return "Welcome to the LLDB curses GUI.\n\n"
6367            "Press the TAB key to change the selected view.\n"
6368            "Each view has its own keyboard shortcuts, press 'h' to open a "
6369            "dialog to display them.\n\n"
6370            "Common key bindings for all views:";
6371   }
6372 
WindowDelegateGetKeyHelp()6373   KeyHelp *WindowDelegateGetKeyHelp() override {
6374     static curses::KeyHelp g_source_view_key_help[] = {
6375         {'\t', "Select next view"},
6376         {KEY_BTAB, "Select previous view"},
6377         {'h', "Show help dialog with view specific key bindings"},
6378         {',', "Page up"},
6379         {'.', "Page down"},
6380         {KEY_UP, "Select previous"},
6381         {KEY_DOWN, "Select next"},
6382         {KEY_LEFT, "Unexpand or select parent"},
6383         {KEY_RIGHT, "Expand"},
6384         {KEY_PPAGE, "Page up"},
6385         {KEY_NPAGE, "Page down"},
6386         {'\0', nullptr}};
6387     return g_source_view_key_help;
6388   }
6389 
MenuDelegateAction(Menu & menu)6390   MenuActionResult MenuDelegateAction(Menu &menu) override {
6391     switch (menu.GetIdentifier()) {
6392     case eMenuID_TargetCreate: {
6393       WindowSP main_window_sp = m_app.GetMainWindow();
6394       FormDelegateSP form_delegate_sp =
6395           FormDelegateSP(new TargetCreateFormDelegate(m_debugger));
6396       Rect bounds = main_window_sp->GetCenteredRect(80, 19);
6397       WindowSP form_window_sp = main_window_sp->CreateSubWindow(
6398           form_delegate_sp->GetName().c_str(), bounds, true);
6399       WindowDelegateSP window_delegate_sp =
6400           WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
6401       form_window_sp->SetDelegate(window_delegate_sp);
6402       return MenuActionResult::Handled;
6403     }
6404     case eMenuID_ThreadStepIn: {
6405       ExecutionContext exe_ctx =
6406           m_debugger.GetCommandInterpreter().GetExecutionContext();
6407       if (exe_ctx.HasThreadScope()) {
6408         Process *process = exe_ctx.GetProcessPtr();
6409         if (process && process->IsAlive() &&
6410             StateIsStoppedState(process->GetState(), true))
6411           exe_ctx.GetThreadRef().StepIn(true);
6412       }
6413     }
6414       return MenuActionResult::Handled;
6415 
6416     case eMenuID_ThreadStepOut: {
6417       ExecutionContext exe_ctx =
6418           m_debugger.GetCommandInterpreter().GetExecutionContext();
6419       if (exe_ctx.HasThreadScope()) {
6420         Process *process = exe_ctx.GetProcessPtr();
6421         if (process && process->IsAlive() &&
6422             StateIsStoppedState(process->GetState(), true))
6423           exe_ctx.GetThreadRef().StepOut();
6424       }
6425     }
6426       return MenuActionResult::Handled;
6427 
6428     case eMenuID_ThreadStepOver: {
6429       ExecutionContext exe_ctx =
6430           m_debugger.GetCommandInterpreter().GetExecutionContext();
6431       if (exe_ctx.HasThreadScope()) {
6432         Process *process = exe_ctx.GetProcessPtr();
6433         if (process && process->IsAlive() &&
6434             StateIsStoppedState(process->GetState(), true))
6435           exe_ctx.GetThreadRef().StepOver(true);
6436       }
6437     }
6438       return MenuActionResult::Handled;
6439 
6440     case eMenuID_ProcessAttach: {
6441       WindowSP main_window_sp = m_app.GetMainWindow();
6442       FormDelegateSP form_delegate_sp = FormDelegateSP(
6443           new ProcessAttachFormDelegate(m_debugger, main_window_sp));
6444       Rect bounds = main_window_sp->GetCenteredRect(80, 22);
6445       WindowSP form_window_sp = main_window_sp->CreateSubWindow(
6446           form_delegate_sp->GetName().c_str(), bounds, true);
6447       WindowDelegateSP window_delegate_sp =
6448           WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
6449       form_window_sp->SetDelegate(window_delegate_sp);
6450       return MenuActionResult::Handled;
6451     }
6452     case eMenuID_ProcessLaunch: {
6453       WindowSP main_window_sp = m_app.GetMainWindow();
6454       FormDelegateSP form_delegate_sp = FormDelegateSP(
6455           new ProcessLaunchFormDelegate(m_debugger, main_window_sp));
6456       Rect bounds = main_window_sp->GetCenteredRect(80, 22);
6457       WindowSP form_window_sp = main_window_sp->CreateSubWindow(
6458           form_delegate_sp->GetName().c_str(), bounds, true);
6459       WindowDelegateSP window_delegate_sp =
6460           WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
6461       form_window_sp->SetDelegate(window_delegate_sp);
6462       return MenuActionResult::Handled;
6463     }
6464 
6465     case eMenuID_ProcessContinue: {
6466       ExecutionContext exe_ctx =
6467           m_debugger.GetCommandInterpreter().GetExecutionContext();
6468       if (exe_ctx.HasProcessScope()) {
6469         Process *process = exe_ctx.GetProcessPtr();
6470         if (process && process->IsAlive() &&
6471             StateIsStoppedState(process->GetState(), true))
6472           process->Resume();
6473       }
6474     }
6475       return MenuActionResult::Handled;
6476 
6477     case eMenuID_ProcessKill: {
6478       ExecutionContext exe_ctx =
6479           m_debugger.GetCommandInterpreter().GetExecutionContext();
6480       if (exe_ctx.HasProcessScope()) {
6481         Process *process = exe_ctx.GetProcessPtr();
6482         if (process && process->IsAlive())
6483           process->Destroy(false);
6484       }
6485     }
6486       return MenuActionResult::Handled;
6487 
6488     case eMenuID_ProcessHalt: {
6489       ExecutionContext exe_ctx =
6490           m_debugger.GetCommandInterpreter().GetExecutionContext();
6491       if (exe_ctx.HasProcessScope()) {
6492         Process *process = exe_ctx.GetProcessPtr();
6493         if (process && process->IsAlive())
6494           process->Halt();
6495       }
6496     }
6497       return MenuActionResult::Handled;
6498 
6499     case eMenuID_ProcessDetachResume:
6500     case eMenuID_ProcessDetachSuspended: {
6501       ExecutionContext exe_ctx =
6502           m_debugger.GetCommandInterpreter().GetExecutionContext();
6503       if (exe_ctx.HasProcessScope()) {
6504         Process *process = exe_ctx.GetProcessPtr();
6505         if (process && process->IsAlive())
6506           process->Detach(menu.GetIdentifier() ==
6507                           eMenuID_ProcessDetachSuspended);
6508       }
6509     }
6510       return MenuActionResult::Handled;
6511 
6512     case eMenuID_Process: {
6513       // Populate the menu with all of the threads if the process is stopped
6514       // when the Process menu gets selected and is about to display its
6515       // submenu.
6516       Menus &submenus = menu.GetSubmenus();
6517       ExecutionContext exe_ctx =
6518           m_debugger.GetCommandInterpreter().GetExecutionContext();
6519       Process *process = exe_ctx.GetProcessPtr();
6520       if (process && process->IsAlive() &&
6521           StateIsStoppedState(process->GetState(), true)) {
6522         if (submenus.size() == 7)
6523           menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
6524         else if (submenus.size() > 8)
6525           submenus.erase(submenus.begin() + 8, submenus.end());
6526 
6527         ThreadList &threads = process->GetThreadList();
6528         std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
6529         size_t num_threads = threads.GetSize();
6530         for (size_t i = 0; i < num_threads; ++i) {
6531           ThreadSP thread_sp = threads.GetThreadAtIndex(i);
6532           char menu_char = '\0';
6533           if (i < 9)
6534             menu_char = '1' + i;
6535           StreamString thread_menu_title;
6536           thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID());
6537           const char *thread_name = thread_sp->GetName();
6538           if (thread_name && thread_name[0])
6539             thread_menu_title.Printf(" %s", thread_name);
6540           else {
6541             const char *queue_name = thread_sp->GetQueueName();
6542             if (queue_name && queue_name[0])
6543               thread_menu_title.Printf(" %s", queue_name);
6544           }
6545           menu.AddSubmenu(
6546               MenuSP(new Menu(thread_menu_title.GetString().str().c_str(),
6547                               nullptr, menu_char, thread_sp->GetID())));
6548         }
6549       } else if (submenus.size() > 7) {
6550         // Remove the separator and any other thread submenu items that were
6551         // previously added
6552         submenus.erase(submenus.begin() + 7, submenus.end());
6553       }
6554       // Since we are adding and removing items we need to recalculate the
6555       // name lengths
6556       menu.RecalculateNameLengths();
6557     }
6558       return MenuActionResult::Handled;
6559 
6560     case eMenuID_ViewVariables: {
6561       WindowSP main_window_sp = m_app.GetMainWindow();
6562       WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
6563       WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
6564       WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
6565       const Rect source_bounds = source_window_sp->GetBounds();
6566 
6567       if (variables_window_sp) {
6568         const Rect variables_bounds = variables_window_sp->GetBounds();
6569 
6570         main_window_sp->RemoveSubWindow(variables_window_sp.get());
6571 
6572         if (registers_window_sp) {
6573           // We have a registers window, so give all the area back to the
6574           // registers window
6575           Rect registers_bounds = variables_bounds;
6576           registers_bounds.size.width = source_bounds.size.width;
6577           registers_window_sp->SetBounds(registers_bounds);
6578         } else {
6579           // We have no registers window showing so give the bottom area back
6580           // to the source view
6581           source_window_sp->Resize(source_bounds.size.width,
6582                                    source_bounds.size.height +
6583                                        variables_bounds.size.height);
6584         }
6585       } else {
6586         Rect new_variables_rect;
6587         if (registers_window_sp) {
6588           // We have a registers window so split the area of the registers
6589           // window into two columns where the left hand side will be the
6590           // variables and the right hand side will be the registers
6591           const Rect variables_bounds = registers_window_sp->GetBounds();
6592           Rect new_registers_rect;
6593           variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect,
6594                                                    new_registers_rect);
6595           registers_window_sp->SetBounds(new_registers_rect);
6596         } else {
6597           // No registers window, grab the bottom part of the source window
6598           Rect new_source_rect;
6599           source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
6600                                                   new_variables_rect);
6601           source_window_sp->SetBounds(new_source_rect);
6602         }
6603         WindowSP new_window_sp = main_window_sp->CreateSubWindow(
6604             "Variables", new_variables_rect, false);
6605         new_window_sp->SetDelegate(
6606             WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
6607       }
6608       touchwin(stdscr);
6609     }
6610       return MenuActionResult::Handled;
6611 
6612     case eMenuID_ViewRegisters: {
6613       WindowSP main_window_sp = m_app.GetMainWindow();
6614       WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
6615       WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
6616       WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
6617       const Rect source_bounds = source_window_sp->GetBounds();
6618 
6619       if (registers_window_sp) {
6620         if (variables_window_sp) {
6621           const Rect variables_bounds = variables_window_sp->GetBounds();
6622 
6623           // We have a variables window, so give all the area back to the
6624           // variables window
6625           variables_window_sp->Resize(variables_bounds.size.width +
6626                                           registers_window_sp->GetWidth(),
6627                                       variables_bounds.size.height);
6628         } else {
6629           // We have no variables window showing so give the bottom area back
6630           // to the source view
6631           source_window_sp->Resize(source_bounds.size.width,
6632                                    source_bounds.size.height +
6633                                        registers_window_sp->GetHeight());
6634         }
6635         main_window_sp->RemoveSubWindow(registers_window_sp.get());
6636       } else {
6637         Rect new_regs_rect;
6638         if (variables_window_sp) {
6639           // We have a variables window, split it into two columns where the
6640           // left hand side will be the variables and the right hand side will
6641           // be the registers
6642           const Rect variables_bounds = variables_window_sp->GetBounds();
6643           Rect new_vars_rect;
6644           variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect,
6645                                                    new_regs_rect);
6646           variables_window_sp->SetBounds(new_vars_rect);
6647         } else {
6648           // No variables window, grab the bottom part of the source window
6649           Rect new_source_rect;
6650           source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
6651                                                   new_regs_rect);
6652           source_window_sp->SetBounds(new_source_rect);
6653         }
6654         WindowSP new_window_sp =
6655             main_window_sp->CreateSubWindow("Registers", new_regs_rect, false);
6656         new_window_sp->SetDelegate(
6657             WindowDelegateSP(new RegistersWindowDelegate(m_debugger)));
6658       }
6659       touchwin(stdscr);
6660     }
6661       return MenuActionResult::Handled;
6662 
6663     case eMenuID_ViewBreakpoints: {
6664       WindowSP main_window_sp = m_app.GetMainWindow();
6665       WindowSP threads_window_sp = main_window_sp->FindSubWindow("Threads");
6666       WindowSP breakpoints_window_sp =
6667           main_window_sp->FindSubWindow("Breakpoints");
6668       const Rect threads_bounds = threads_window_sp->GetBounds();
6669 
6670       // If a breakpoints window already exists, remove it and give the area
6671       // it used to occupy to the threads window. If it doesn't exist, split
6672       // the threads window horizontally into two windows where the top window
6673       // is the threads window and the bottom window is a newly added
6674       // breakpoints window.
6675       if (breakpoints_window_sp) {
6676         threads_window_sp->Resize(threads_bounds.size.width,
6677                                   threads_bounds.size.height +
6678                                       breakpoints_window_sp->GetHeight());
6679         main_window_sp->RemoveSubWindow(breakpoints_window_sp.get());
6680       } else {
6681         Rect new_threads_bounds, breakpoints_bounds;
6682         threads_bounds.HorizontalSplitPercentage(0.70, new_threads_bounds,
6683                                                  breakpoints_bounds);
6684         threads_window_sp->SetBounds(new_threads_bounds);
6685         breakpoints_window_sp = main_window_sp->CreateSubWindow(
6686             "Breakpoints", breakpoints_bounds, false);
6687         TreeDelegateSP breakpoints_delegate_sp(
6688             new BreakpointsTreeDelegate(m_debugger));
6689         breakpoints_window_sp->SetDelegate(WindowDelegateSP(
6690             new TreeWindowDelegate(m_debugger, breakpoints_delegate_sp)));
6691       }
6692       touchwin(stdscr);
6693       return MenuActionResult::Handled;
6694     }
6695 
6696     case eMenuID_HelpGUIHelp:
6697       m_app.GetMainWindow()->CreateHelpSubwindow();
6698       return MenuActionResult::Handled;
6699 
6700     default:
6701       break;
6702     }
6703 
6704     return MenuActionResult::NotHandled;
6705   }
6706 
6707 protected:
6708   Application &m_app;
6709   Debugger &m_debugger;
6710 };
6711 
6712 class StatusBarWindowDelegate : public WindowDelegate {
6713 public:
StatusBarWindowDelegate(Debugger & debugger)6714   StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) {
6715     FormatEntity::Parse("Thread: ${thread.id%tid}", m_format);
6716   }
6717 
6718   ~StatusBarWindowDelegate() override = default;
6719 
WindowDelegateDraw(Window & window,bool force)6720   bool WindowDelegateDraw(Window &window, bool force) override {
6721     ExecutionContext exe_ctx =
6722         m_debugger.GetCommandInterpreter().GetExecutionContext();
6723     Process *process = exe_ctx.GetProcessPtr();
6724     Thread *thread = exe_ctx.GetThreadPtr();
6725     StackFrame *frame = exe_ctx.GetFramePtr();
6726     window.Erase();
6727     window.SetBackground(BlackOnWhite);
6728     window.MoveCursor(0, 0);
6729     if (process) {
6730       const StateType state = process->GetState();
6731       window.Printf("Process: %5" PRIu64 " %10s", process->GetID(),
6732                     StateAsCString(state));
6733 
6734       if (StateIsStoppedState(state, true)) {
6735         StreamString strm;
6736         if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx,
6737                                            nullptr, nullptr, false, false)) {
6738           window.MoveCursor(40, 0);
6739           window.PutCStringTruncated(1, strm.GetString().str().c_str());
6740         }
6741 
6742         window.MoveCursor(60, 0);
6743         if (frame)
6744           window.Printf("Frame: %3u  PC = 0x%16.16" PRIx64,
6745                         frame->GetFrameIndex(),
6746                         frame->GetFrameCodeAddress().GetOpcodeLoadAddress(
6747                             exe_ctx.GetTargetPtr()));
6748       } else if (state == eStateExited) {
6749         const char *exit_desc = process->GetExitDescription();
6750         const int exit_status = process->GetExitStatus();
6751         if (exit_desc && exit_desc[0])
6752           window.Printf(" with status = %i (%s)", exit_status, exit_desc);
6753         else
6754           window.Printf(" with status = %i", exit_status);
6755       }
6756     }
6757     return true;
6758   }
6759 
6760 protected:
6761   Debugger &m_debugger;
6762   FormatEntity::Entry m_format;
6763 };
6764 
6765 class SourceFileWindowDelegate : public WindowDelegate {
6766 public:
SourceFileWindowDelegate(Debugger & debugger)6767   SourceFileWindowDelegate(Debugger &debugger)
6768       : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(),
6769         m_disassembly_scope(nullptr), m_disassembly_sp(), m_disassembly_range(),
6770         m_title(), m_tid(LLDB_INVALID_THREAD_ID), m_line_width(4),
6771         m_selected_line(0), m_pc_line(0), m_stop_id(0), m_frame_idx(UINT32_MAX),
6772         m_first_visible_line(0), m_first_visible_column(0), m_min_x(0),
6773         m_min_y(0), m_max_x(0), m_max_y(0) {}
6774 
6775   ~SourceFileWindowDelegate() override = default;
6776 
Update(const SymbolContext & sc)6777   void Update(const SymbolContext &sc) { m_sc = sc; }
6778 
NumVisibleLines() const6779   uint32_t NumVisibleLines() const { return m_max_y - m_min_y; }
6780 
WindowDelegateGetHelpText()6781   const char *WindowDelegateGetHelpText() override {
6782     return "Source/Disassembly window keyboard shortcuts:";
6783   }
6784 
WindowDelegateGetKeyHelp()6785   KeyHelp *WindowDelegateGetKeyHelp() override {
6786     static curses::KeyHelp g_source_view_key_help[] = {
6787         {KEY_RETURN, "Run to selected line with one shot breakpoint"},
6788         {KEY_UP, "Select previous source line"},
6789         {KEY_DOWN, "Select next source line"},
6790         {KEY_LEFT, "Scroll to the left"},
6791         {KEY_RIGHT, "Scroll to the right"},
6792         {KEY_PPAGE, "Page up"},
6793         {KEY_NPAGE, "Page down"},
6794         {'b', "Set breakpoint on selected source/disassembly line"},
6795         {'c', "Continue process"},
6796         {'D', "Detach with process suspended"},
6797         {'h', "Show help dialog"},
6798         {'n', "Step over (source line)"},
6799         {'N', "Step over (single instruction)"},
6800         {'f', "Step out (finish)"},
6801         {'s', "Step in (source line)"},
6802         {'S', "Step in (single instruction)"},
6803         {'u', "Frame up"},
6804         {'d', "Frame down"},
6805         {',', "Page up"},
6806         {'.', "Page down"},
6807         {'\0', nullptr}};
6808     return g_source_view_key_help;
6809   }
6810 
WindowDelegateDraw(Window & window,bool force)6811   bool WindowDelegateDraw(Window &window, bool force) override {
6812     ExecutionContext exe_ctx =
6813         m_debugger.GetCommandInterpreter().GetExecutionContext();
6814     Process *process = exe_ctx.GetProcessPtr();
6815     Thread *thread = nullptr;
6816 
6817     bool update_location = false;
6818     if (process) {
6819       StateType state = process->GetState();
6820       if (StateIsStoppedState(state, true)) {
6821         // We are stopped, so it is ok to
6822         update_location = true;
6823       }
6824     }
6825 
6826     m_min_x = 1;
6827     m_min_y = 2;
6828     m_max_x = window.GetMaxX() - 1;
6829     m_max_y = window.GetMaxY() - 1;
6830 
6831     const uint32_t num_visible_lines = NumVisibleLines();
6832     StackFrameSP frame_sp;
6833     bool set_selected_line_to_pc = false;
6834 
6835     if (update_location) {
6836       const bool process_alive = process ? process->IsAlive() : false;
6837       bool thread_changed = false;
6838       if (process_alive) {
6839         thread = exe_ctx.GetThreadPtr();
6840         if (thread) {
6841           frame_sp = thread->GetSelectedFrame();
6842           auto tid = thread->GetID();
6843           thread_changed = tid != m_tid;
6844           m_tid = tid;
6845         } else {
6846           if (m_tid != LLDB_INVALID_THREAD_ID) {
6847             thread_changed = true;
6848             m_tid = LLDB_INVALID_THREAD_ID;
6849           }
6850         }
6851       }
6852       const uint32_t stop_id = process ? process->GetStopID() : 0;
6853       const bool stop_id_changed = stop_id != m_stop_id;
6854       bool frame_changed = false;
6855       m_stop_id = stop_id;
6856       m_title.Clear();
6857       if (frame_sp) {
6858         m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything);
6859         if (m_sc.module_sp) {
6860           m_title.Printf(
6861               "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString());
6862           ConstString func_name = m_sc.GetFunctionName();
6863           if (func_name)
6864             m_title.Printf("`%s", func_name.GetCString());
6865         }
6866         const uint32_t frame_idx = frame_sp->GetFrameIndex();
6867         frame_changed = frame_idx != m_frame_idx;
6868         m_frame_idx = frame_idx;
6869       } else {
6870         m_sc.Clear(true);
6871         frame_changed = m_frame_idx != UINT32_MAX;
6872         m_frame_idx = UINT32_MAX;
6873       }
6874 
6875       const bool context_changed =
6876           thread_changed || frame_changed || stop_id_changed;
6877 
6878       if (process_alive) {
6879         if (m_sc.line_entry.IsValid()) {
6880           m_pc_line = m_sc.line_entry.line;
6881           if (m_pc_line != UINT32_MAX)
6882             --m_pc_line; // Convert to zero based line number...
6883           // Update the selected line if the stop ID changed...
6884           if (context_changed)
6885             m_selected_line = m_pc_line;
6886 
6887           if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) {
6888             // Same file, nothing to do, we should either have the lines or
6889             // not (source file missing)
6890             if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) {
6891               if (m_selected_line >= m_first_visible_line + num_visible_lines)
6892                 m_first_visible_line = m_selected_line - 10;
6893             } else {
6894               if (m_selected_line > 10)
6895                 m_first_visible_line = m_selected_line - 10;
6896               else
6897                 m_first_visible_line = 0;
6898             }
6899           } else {
6900             // File changed, set selected line to the line with the PC
6901             m_selected_line = m_pc_line;
6902             m_file_sp =
6903                 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file);
6904             if (m_file_sp) {
6905               const size_t num_lines = m_file_sp->GetNumLines();
6906               m_line_width = 1;
6907               for (size_t n = num_lines; n >= 10; n = n / 10)
6908                 ++m_line_width;
6909 
6910               if (num_lines < num_visible_lines ||
6911                   m_selected_line < num_visible_lines)
6912                 m_first_visible_line = 0;
6913               else
6914                 m_first_visible_line = m_selected_line - 10;
6915             }
6916           }
6917         } else {
6918           m_file_sp.reset();
6919         }
6920 
6921         if (!m_file_sp || m_file_sp->GetNumLines() == 0) {
6922           // Show disassembly
6923           bool prefer_file_cache = false;
6924           if (m_sc.function) {
6925             if (m_disassembly_scope != m_sc.function) {
6926               m_disassembly_scope = m_sc.function;
6927               m_disassembly_sp = m_sc.function->GetInstructions(
6928                   exe_ctx, nullptr, !prefer_file_cache);
6929               if (m_disassembly_sp) {
6930                 set_selected_line_to_pc = true;
6931                 m_disassembly_range = m_sc.function->GetAddressRange();
6932               } else {
6933                 m_disassembly_range.Clear();
6934               }
6935             } else {
6936               set_selected_line_to_pc = context_changed;
6937             }
6938           } else if (m_sc.symbol) {
6939             if (m_disassembly_scope != m_sc.symbol) {
6940               m_disassembly_scope = m_sc.symbol;
6941               m_disassembly_sp = m_sc.symbol->GetInstructions(
6942                   exe_ctx, nullptr, prefer_file_cache);
6943               if (m_disassembly_sp) {
6944                 set_selected_line_to_pc = true;
6945                 m_disassembly_range.GetBaseAddress() =
6946                     m_sc.symbol->GetAddress();
6947                 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize());
6948               } else {
6949                 m_disassembly_range.Clear();
6950               }
6951             } else {
6952               set_selected_line_to_pc = context_changed;
6953             }
6954           }
6955         }
6956       } else {
6957         m_pc_line = UINT32_MAX;
6958       }
6959     }
6960 
6961     const int window_width = window.GetWidth();
6962     window.Erase();
6963     window.DrawTitleBox("Sources");
6964     if (!m_title.GetString().empty()) {
6965       window.AttributeOn(A_REVERSE);
6966       window.MoveCursor(1, 1);
6967       window.PutChar(' ');
6968       window.PutCStringTruncated(1, m_title.GetString().str().c_str());
6969       int x = window.GetCursorX();
6970       if (x < window_width - 1) {
6971         window.Printf("%*s", window_width - x - 1, "");
6972       }
6973       window.AttributeOff(A_REVERSE);
6974     }
6975 
6976     Target *target = exe_ctx.GetTargetPtr();
6977     const size_t num_source_lines = GetNumSourceLines();
6978     if (num_source_lines > 0) {
6979       // Display source
6980       BreakpointLines bp_lines;
6981       if (target) {
6982         BreakpointList &bp_list = target->GetBreakpointList();
6983         const size_t num_bps = bp_list.GetSize();
6984         for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
6985           BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
6986           const size_t num_bps_locs = bp_sp->GetNumLocations();
6987           for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
6988             BreakpointLocationSP bp_loc_sp =
6989                 bp_sp->GetLocationAtIndex(bp_loc_idx);
6990             LineEntry bp_loc_line_entry;
6991             if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
6992                     bp_loc_line_entry)) {
6993               if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) {
6994                 bp_lines.insert(bp_loc_line_entry.line);
6995               }
6996             }
6997           }
6998         }
6999       }
7000 
7001       const attr_t selected_highlight_attr = A_REVERSE;
7002       const attr_t pc_highlight_attr = COLOR_PAIR(BlackOnBlue);
7003 
7004       for (size_t i = 0; i < num_visible_lines; ++i) {
7005         const uint32_t curr_line = m_first_visible_line + i;
7006         if (curr_line < num_source_lines) {
7007           const int line_y = m_min_y + i;
7008           window.MoveCursor(1, line_y);
7009           const bool is_pc_line = curr_line == m_pc_line;
7010           const bool line_is_selected = m_selected_line == curr_line;
7011           // Highlight the line as the PC line first, then if the selected
7012           // line isn't the same as the PC line, highlight it differently
7013           attr_t highlight_attr = 0;
7014           attr_t bp_attr = 0;
7015           if (is_pc_line)
7016             highlight_attr = pc_highlight_attr;
7017           else if (line_is_selected)
7018             highlight_attr = selected_highlight_attr;
7019 
7020           if (bp_lines.find(curr_line + 1) != bp_lines.end())
7021             bp_attr = COLOR_PAIR(BlackOnWhite);
7022 
7023           if (bp_attr)
7024             window.AttributeOn(bp_attr);
7025 
7026           window.Printf(" %*u ", m_line_width, curr_line + 1);
7027 
7028           if (bp_attr)
7029             window.AttributeOff(bp_attr);
7030 
7031           window.PutChar(ACS_VLINE);
7032           // Mark the line with the PC with a diamond
7033           if (is_pc_line)
7034             window.PutChar(ACS_DIAMOND);
7035           else
7036             window.PutChar(' ');
7037 
7038           if (highlight_attr)
7039             window.AttributeOn(highlight_attr);
7040 
7041           StreamString lineStream;
7042           m_file_sp->DisplaySourceLines(curr_line + 1, {}, 0, 0, &lineStream);
7043           StringRef line = lineStream.GetString();
7044           if (line.endswith("\n"))
7045             line = line.drop_back();
7046           bool wasWritten = window.OutputColoredStringTruncated(
7047               1, line, m_first_visible_column, line_is_selected);
7048           if (line_is_selected && !wasWritten) {
7049             // Draw an empty space to show the selected line if empty,
7050             // or draw '<' if nothing is visible because of scrolling too much
7051             // to the right.
7052             window.PutCStringTruncated(
7053                 1, line.empty() && m_first_visible_column == 0 ? " " : "<");
7054           }
7055 
7056           if (is_pc_line && frame_sp &&
7057               frame_sp->GetConcreteFrameIndex() == 0) {
7058             StopInfoSP stop_info_sp;
7059             if (thread)
7060               stop_info_sp = thread->GetStopInfo();
7061             if (stop_info_sp) {
7062               const char *stop_description = stop_info_sp->GetDescription();
7063               if (stop_description && stop_description[0]) {
7064                 size_t stop_description_len = strlen(stop_description);
7065                 int desc_x = window_width - stop_description_len - 16;
7066                 if (desc_x - window.GetCursorX() > 0)
7067                   window.Printf("%*s", desc_x - window.GetCursorX(), "");
7068                 window.MoveCursor(window_width - stop_description_len - 16,
7069                                   line_y);
7070                 const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue);
7071                 window.AttributeOn(stop_reason_attr);
7072                 window.PrintfTruncated(1, " <<< Thread %u: %s ",
7073                                        thread->GetIndexID(), stop_description);
7074                 window.AttributeOff(stop_reason_attr);
7075               }
7076             } else {
7077               window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
7078             }
7079           }
7080           if (highlight_attr)
7081             window.AttributeOff(highlight_attr);
7082         } else {
7083           break;
7084         }
7085       }
7086     } else {
7087       size_t num_disassembly_lines = GetNumDisassemblyLines();
7088       if (num_disassembly_lines > 0) {
7089         // Display disassembly
7090         BreakpointAddrs bp_file_addrs;
7091         Target *target = exe_ctx.GetTargetPtr();
7092         if (target) {
7093           BreakpointList &bp_list = target->GetBreakpointList();
7094           const size_t num_bps = bp_list.GetSize();
7095           for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7096             BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7097             const size_t num_bps_locs = bp_sp->GetNumLocations();
7098             for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs;
7099                  ++bp_loc_idx) {
7100               BreakpointLocationSP bp_loc_sp =
7101                   bp_sp->GetLocationAtIndex(bp_loc_idx);
7102               LineEntry bp_loc_line_entry;
7103               const lldb::addr_t file_addr =
7104                   bp_loc_sp->GetAddress().GetFileAddress();
7105               if (file_addr != LLDB_INVALID_ADDRESS) {
7106                 if (m_disassembly_range.ContainsFileAddress(file_addr))
7107                   bp_file_addrs.insert(file_addr);
7108               }
7109             }
7110           }
7111         }
7112 
7113         const attr_t selected_highlight_attr = A_REVERSE;
7114         const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue);
7115 
7116         StreamString strm;
7117 
7118         InstructionList &insts = m_disassembly_sp->GetInstructionList();
7119         Address pc_address;
7120 
7121         if (frame_sp)
7122           pc_address = frame_sp->GetFrameCodeAddress();
7123         const uint32_t pc_idx =
7124             pc_address.IsValid()
7125                 ? insts.GetIndexOfInstructionAtAddress(pc_address)
7126                 : UINT32_MAX;
7127         if (set_selected_line_to_pc) {
7128           m_selected_line = pc_idx;
7129         }
7130 
7131         const uint32_t non_visible_pc_offset = (num_visible_lines / 5);
7132         if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines)
7133           m_first_visible_line = 0;
7134 
7135         if (pc_idx < num_disassembly_lines) {
7136           if (pc_idx < static_cast<uint32_t>(m_first_visible_line) ||
7137               pc_idx >= m_first_visible_line + num_visible_lines)
7138             m_first_visible_line = pc_idx - non_visible_pc_offset;
7139         }
7140 
7141         for (size_t i = 0; i < num_visible_lines; ++i) {
7142           const uint32_t inst_idx = m_first_visible_line + i;
7143           Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get();
7144           if (!inst)
7145             break;
7146 
7147           const int line_y = m_min_y + i;
7148           window.MoveCursor(1, line_y);
7149           const bool is_pc_line = frame_sp && inst_idx == pc_idx;
7150           const bool line_is_selected = m_selected_line == inst_idx;
7151           // Highlight the line as the PC line first, then if the selected
7152           // line isn't the same as the PC line, highlight it differently
7153           attr_t highlight_attr = 0;
7154           attr_t bp_attr = 0;
7155           if (is_pc_line)
7156             highlight_attr = pc_highlight_attr;
7157           else if (line_is_selected)
7158             highlight_attr = selected_highlight_attr;
7159 
7160           if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) !=
7161               bp_file_addrs.end())
7162             bp_attr = COLOR_PAIR(BlackOnWhite);
7163 
7164           if (bp_attr)
7165             window.AttributeOn(bp_attr);
7166 
7167           window.Printf(" 0x%16.16llx ",
7168                         static_cast<unsigned long long>(
7169                             inst->GetAddress().GetLoadAddress(target)));
7170 
7171           if (bp_attr)
7172             window.AttributeOff(bp_attr);
7173 
7174           window.PutChar(ACS_VLINE);
7175           // Mark the line with the PC with a diamond
7176           if (is_pc_line)
7177             window.PutChar(ACS_DIAMOND);
7178           else
7179             window.PutChar(' ');
7180 
7181           if (highlight_attr)
7182             window.AttributeOn(highlight_attr);
7183 
7184           const char *mnemonic = inst->GetMnemonic(&exe_ctx);
7185           const char *operands = inst->GetOperands(&exe_ctx);
7186           const char *comment = inst->GetComment(&exe_ctx);
7187 
7188           if (mnemonic != nullptr && mnemonic[0] == '\0')
7189             mnemonic = nullptr;
7190           if (operands != nullptr && operands[0] == '\0')
7191             operands = nullptr;
7192           if (comment != nullptr && comment[0] == '\0')
7193             comment = nullptr;
7194 
7195           strm.Clear();
7196 
7197           if (mnemonic != nullptr && operands != nullptr && comment != nullptr)
7198             strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment);
7199           else if (mnemonic != nullptr && operands != nullptr)
7200             strm.Printf("%-8s %s", mnemonic, operands);
7201           else if (mnemonic != nullptr)
7202             strm.Printf("%s", mnemonic);
7203 
7204           int right_pad = 1;
7205           window.PutCStringTruncated(
7206               right_pad,
7207               strm.GetString().substr(m_first_visible_column).data());
7208 
7209           if (is_pc_line && frame_sp &&
7210               frame_sp->GetConcreteFrameIndex() == 0) {
7211             StopInfoSP stop_info_sp;
7212             if (thread)
7213               stop_info_sp = thread->GetStopInfo();
7214             if (stop_info_sp) {
7215               const char *stop_description = stop_info_sp->GetDescription();
7216               if (stop_description && stop_description[0]) {
7217                 size_t stop_description_len = strlen(stop_description);
7218                 int desc_x = window_width - stop_description_len - 16;
7219                 if (desc_x - window.GetCursorX() > 0)
7220                   window.Printf("%*s", desc_x - window.GetCursorX(), "");
7221                 window.MoveCursor(window_width - stop_description_len - 15,
7222                                   line_y);
7223                 window.PrintfTruncated(1, "<<< Thread %u: %s ",
7224                                        thread->GetIndexID(), stop_description);
7225               }
7226             } else {
7227               window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
7228             }
7229           }
7230           if (highlight_attr)
7231             window.AttributeOff(highlight_attr);
7232         }
7233       }
7234     }
7235     return true; // Drawing handled
7236   }
7237 
GetNumLines()7238   size_t GetNumLines() {
7239     size_t num_lines = GetNumSourceLines();
7240     if (num_lines == 0)
7241       num_lines = GetNumDisassemblyLines();
7242     return num_lines;
7243   }
7244 
GetNumSourceLines() const7245   size_t GetNumSourceLines() const {
7246     if (m_file_sp)
7247       return m_file_sp->GetNumLines();
7248     return 0;
7249   }
7250 
GetNumDisassemblyLines() const7251   size_t GetNumDisassemblyLines() const {
7252     if (m_disassembly_sp)
7253       return m_disassembly_sp->GetInstructionList().GetSize();
7254     return 0;
7255   }
7256 
WindowDelegateHandleChar(Window & window,int c)7257   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
7258     const uint32_t num_visible_lines = NumVisibleLines();
7259     const size_t num_lines = GetNumLines();
7260 
7261     switch (c) {
7262     case ',':
7263     case KEY_PPAGE:
7264       // Page up key
7265       if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines)
7266         m_first_visible_line -= num_visible_lines;
7267       else
7268         m_first_visible_line = 0;
7269       m_selected_line = m_first_visible_line;
7270       return eKeyHandled;
7271 
7272     case '.':
7273     case KEY_NPAGE:
7274       // Page down key
7275       {
7276         if (m_first_visible_line + num_visible_lines < num_lines)
7277           m_first_visible_line += num_visible_lines;
7278         else if (num_lines < num_visible_lines)
7279           m_first_visible_line = 0;
7280         else
7281           m_first_visible_line = num_lines - num_visible_lines;
7282         m_selected_line = m_first_visible_line;
7283       }
7284       return eKeyHandled;
7285 
7286     case KEY_UP:
7287       if (m_selected_line > 0) {
7288         m_selected_line--;
7289         if (static_cast<size_t>(m_first_visible_line) > m_selected_line)
7290           m_first_visible_line = m_selected_line;
7291       }
7292       return eKeyHandled;
7293 
7294     case KEY_DOWN:
7295       if (m_selected_line + 1 < num_lines) {
7296         m_selected_line++;
7297         if (m_first_visible_line + num_visible_lines < m_selected_line)
7298           m_first_visible_line++;
7299       }
7300       return eKeyHandled;
7301 
7302     case KEY_LEFT:
7303       if (m_first_visible_column > 0)
7304         --m_first_visible_column;
7305       return eKeyHandled;
7306 
7307     case KEY_RIGHT:
7308       ++m_first_visible_column;
7309       return eKeyHandled;
7310 
7311     case '\r':
7312     case '\n':
7313     case KEY_ENTER:
7314       // Set a breakpoint and run to the line using a one shot breakpoint
7315       if (GetNumSourceLines() > 0) {
7316         ExecutionContext exe_ctx =
7317             m_debugger.GetCommandInterpreter().GetExecutionContext();
7318         if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) {
7319           BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
7320               nullptr, // Don't limit the breakpoint to certain modules
7321               m_file_sp->GetFileSpec(), // Source file
7322               m_selected_line +
7323                   1, // Source line number (m_selected_line is zero based)
7324               0,     // Unspecified column.
7325               0,     // No offset
7326               eLazyBoolCalculate,  // Check inlines using global setting
7327               eLazyBoolCalculate,  // Skip prologue using global setting,
7328               false,               // internal
7329               false,               // request_hardware
7330               eLazyBoolCalculate); // move_to_nearest_code
7331           // Make breakpoint one shot
7332           bp_sp->GetOptions().SetOneShot(true);
7333           exe_ctx.GetProcessRef().Resume();
7334         }
7335       } else if (m_selected_line < GetNumDisassemblyLines()) {
7336         const Instruction *inst = m_disassembly_sp->GetInstructionList()
7337                                       .GetInstructionAtIndex(m_selected_line)
7338                                       .get();
7339         ExecutionContext exe_ctx =
7340             m_debugger.GetCommandInterpreter().GetExecutionContext();
7341         if (exe_ctx.HasTargetScope()) {
7342           Address addr = inst->GetAddress();
7343           BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
7344               addr,   // lldb_private::Address
7345               false,  // internal
7346               false); // request_hardware
7347           // Make breakpoint one shot
7348           bp_sp->GetOptions().SetOneShot(true);
7349           exe_ctx.GetProcessRef().Resume();
7350         }
7351       }
7352       return eKeyHandled;
7353 
7354     case 'b': // 'b' == toggle breakpoint on currently selected line
7355       ToggleBreakpointOnSelectedLine();
7356       return eKeyHandled;
7357 
7358     case 'D': // 'D' == detach and keep stopped
7359     {
7360       ExecutionContext exe_ctx =
7361           m_debugger.GetCommandInterpreter().GetExecutionContext();
7362       if (exe_ctx.HasProcessScope())
7363         exe_ctx.GetProcessRef().Detach(true);
7364     }
7365       return eKeyHandled;
7366 
7367     case 'c':
7368       // 'c' == continue
7369       {
7370         ExecutionContext exe_ctx =
7371             m_debugger.GetCommandInterpreter().GetExecutionContext();
7372         if (exe_ctx.HasProcessScope())
7373           exe_ctx.GetProcessRef().Resume();
7374       }
7375       return eKeyHandled;
7376 
7377     case 'f':
7378       // 'f' == step out (finish)
7379       {
7380         ExecutionContext exe_ctx =
7381             m_debugger.GetCommandInterpreter().GetExecutionContext();
7382         if (exe_ctx.HasThreadScope() &&
7383             StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
7384           exe_ctx.GetThreadRef().StepOut();
7385         }
7386       }
7387       return eKeyHandled;
7388 
7389     case 'n': // 'n' == step over
7390     case 'N': // 'N' == step over instruction
7391     {
7392       ExecutionContext exe_ctx =
7393           m_debugger.GetCommandInterpreter().GetExecutionContext();
7394       if (exe_ctx.HasThreadScope() &&
7395           StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
7396         bool source_step = (c == 'n');
7397         exe_ctx.GetThreadRef().StepOver(source_step);
7398       }
7399     }
7400       return eKeyHandled;
7401 
7402     case 's': // 's' == step into
7403     case 'S': // 'S' == step into instruction
7404     {
7405       ExecutionContext exe_ctx =
7406           m_debugger.GetCommandInterpreter().GetExecutionContext();
7407       if (exe_ctx.HasThreadScope() &&
7408           StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
7409         bool source_step = (c == 's');
7410         exe_ctx.GetThreadRef().StepIn(source_step);
7411       }
7412     }
7413       return eKeyHandled;
7414 
7415     case 'u': // 'u' == frame up
7416     case 'd': // 'd' == frame down
7417     {
7418       ExecutionContext exe_ctx =
7419           m_debugger.GetCommandInterpreter().GetExecutionContext();
7420       if (exe_ctx.HasThreadScope()) {
7421         Thread *thread = exe_ctx.GetThreadPtr();
7422         uint32_t frame_idx = thread->GetSelectedFrameIndex();
7423         if (frame_idx == UINT32_MAX)
7424           frame_idx = 0;
7425         if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount())
7426           ++frame_idx;
7427         else if (c == 'd' && frame_idx > 0)
7428           --frame_idx;
7429         if (thread->SetSelectedFrameByIndex(frame_idx, true))
7430           exe_ctx.SetFrameSP(thread->GetSelectedFrame());
7431       }
7432     }
7433       return eKeyHandled;
7434 
7435     case 'h':
7436       window.CreateHelpSubwindow();
7437       return eKeyHandled;
7438 
7439     default:
7440       break;
7441     }
7442     return eKeyNotHandled;
7443   }
7444 
ToggleBreakpointOnSelectedLine()7445   void ToggleBreakpointOnSelectedLine() {
7446     ExecutionContext exe_ctx =
7447         m_debugger.GetCommandInterpreter().GetExecutionContext();
7448     if (!exe_ctx.HasTargetScope())
7449       return;
7450     if (GetNumSourceLines() > 0) {
7451       // Source file breakpoint.
7452       BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
7453       const size_t num_bps = bp_list.GetSize();
7454       for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7455         BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7456         const size_t num_bps_locs = bp_sp->GetNumLocations();
7457         for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
7458           BreakpointLocationSP bp_loc_sp =
7459               bp_sp->GetLocationAtIndex(bp_loc_idx);
7460           LineEntry bp_loc_line_entry;
7461           if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
7462                   bp_loc_line_entry)) {
7463             if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file &&
7464                 m_selected_line + 1 == bp_loc_line_entry.line) {
7465               bool removed =
7466                   exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
7467               assert(removed);
7468               UNUSED_IF_ASSERT_DISABLED(removed);
7469               return; // Existing breakpoint removed.
7470             }
7471           }
7472         }
7473       }
7474       // No breakpoint found on the location, add it.
7475       BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
7476           nullptr, // Don't limit the breakpoint to certain modules
7477           m_file_sp->GetFileSpec(), // Source file
7478           m_selected_line +
7479               1, // Source line number (m_selected_line is zero based)
7480           0,     // No column specified.
7481           0,     // No offset
7482           eLazyBoolCalculate,  // Check inlines using global setting
7483           eLazyBoolCalculate,  // Skip prologue using global setting,
7484           false,               // internal
7485           false,               // request_hardware
7486           eLazyBoolCalculate); // move_to_nearest_code
7487     } else {
7488       // Disassembly breakpoint.
7489       assert(GetNumDisassemblyLines() > 0);
7490       assert(m_selected_line < GetNumDisassemblyLines());
7491       const Instruction *inst = m_disassembly_sp->GetInstructionList()
7492                                     .GetInstructionAtIndex(m_selected_line)
7493                                     .get();
7494       Address addr = inst->GetAddress();
7495       // Try to find it.
7496       BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
7497       const size_t num_bps = bp_list.GetSize();
7498       for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7499         BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7500         const size_t num_bps_locs = bp_sp->GetNumLocations();
7501         for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
7502           BreakpointLocationSP bp_loc_sp =
7503               bp_sp->GetLocationAtIndex(bp_loc_idx);
7504           LineEntry bp_loc_line_entry;
7505           const lldb::addr_t file_addr =
7506               bp_loc_sp->GetAddress().GetFileAddress();
7507           if (file_addr == addr.GetFileAddress()) {
7508             bool removed =
7509                 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
7510             assert(removed);
7511             UNUSED_IF_ASSERT_DISABLED(removed);
7512             return; // Existing breakpoint removed.
7513           }
7514         }
7515       }
7516       // No breakpoint found on the address, add it.
7517       BreakpointSP bp_sp =
7518           exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address
7519                                                   false,  // internal
7520                                                   false); // request_hardware
7521     }
7522   }
7523 
7524 protected:
7525   typedef std::set<uint32_t> BreakpointLines;
7526   typedef std::set<lldb::addr_t> BreakpointAddrs;
7527 
7528   Debugger &m_debugger;
7529   SymbolContext m_sc;
7530   SourceManager::FileSP m_file_sp;
7531   SymbolContextScope *m_disassembly_scope;
7532   lldb::DisassemblerSP m_disassembly_sp;
7533   AddressRange m_disassembly_range;
7534   StreamString m_title;
7535   lldb::user_id_t m_tid;
7536   int m_line_width;
7537   uint32_t m_selected_line; // The selected line
7538   uint32_t m_pc_line;       // The line with the PC
7539   uint32_t m_stop_id;
7540   uint32_t m_frame_idx;
7541   int m_first_visible_line;
7542   int m_first_visible_column;
7543   int m_min_x;
7544   int m_min_y;
7545   int m_max_x;
7546   int m_max_y;
7547 };
7548 
7549 DisplayOptions ValueObjectListDelegate::g_options = {true};
7550 
IOHandlerCursesGUI(Debugger & debugger)7551 IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger)
7552     : IOHandler(debugger, IOHandler::Type::Curses) {}
7553 
Activate()7554 void IOHandlerCursesGUI::Activate() {
7555   IOHandler::Activate();
7556   if (!m_app_ap) {
7557     m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE());
7558 
7559     // This is both a window and a menu delegate
7560     std::shared_ptr<ApplicationDelegate> app_delegate_sp(
7561         new ApplicationDelegate(*m_app_ap, m_debugger));
7562 
7563     MenuDelegateSP app_menu_delegate_sp =
7564         std::static_pointer_cast<MenuDelegate>(app_delegate_sp);
7565     MenuSP lldb_menu_sp(
7566         new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB));
7567     MenuSP exit_menuitem_sp(
7568         new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit));
7569     exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit);
7570     lldb_menu_sp->AddSubmenu(MenuSP(new Menu(
7571         "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout)));
7572     lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
7573     lldb_menu_sp->AddSubmenu(exit_menuitem_sp);
7574 
7575     MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2),
7576                                    ApplicationDelegate::eMenuID_Target));
7577     target_menu_sp->AddSubmenu(MenuSP(new Menu(
7578         "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate)));
7579     target_menu_sp->AddSubmenu(MenuSP(new Menu(
7580         "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete)));
7581 
7582     MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3),
7583                                     ApplicationDelegate::eMenuID_Process));
7584     process_menu_sp->AddSubmenu(MenuSP(new Menu(
7585         "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach)));
7586     process_menu_sp->AddSubmenu(
7587         MenuSP(new Menu("Detach and resume", nullptr, 'd',
7588                         ApplicationDelegate::eMenuID_ProcessDetachResume)));
7589     process_menu_sp->AddSubmenu(
7590         MenuSP(new Menu("Detach suspended", nullptr, 's',
7591                         ApplicationDelegate::eMenuID_ProcessDetachSuspended)));
7592     process_menu_sp->AddSubmenu(MenuSP(new Menu(
7593         "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch)));
7594     process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
7595     process_menu_sp->AddSubmenu(
7596         MenuSP(new Menu("Continue", nullptr, 'c',
7597                         ApplicationDelegate::eMenuID_ProcessContinue)));
7598     process_menu_sp->AddSubmenu(MenuSP(new Menu(
7599         "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt)));
7600     process_menu_sp->AddSubmenu(MenuSP(new Menu(
7601         "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill)));
7602 
7603     MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4),
7604                                    ApplicationDelegate::eMenuID_Thread));
7605     thread_menu_sp->AddSubmenu(MenuSP(new Menu(
7606         "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn)));
7607     thread_menu_sp->AddSubmenu(
7608         MenuSP(new Menu("Step Over", nullptr, 'v',
7609                         ApplicationDelegate::eMenuID_ThreadStepOver)));
7610     thread_menu_sp->AddSubmenu(MenuSP(new Menu(
7611         "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut)));
7612 
7613     MenuSP view_menu_sp(
7614         new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View));
7615     view_menu_sp->AddSubmenu(
7616         MenuSP(new Menu("Backtrace", nullptr, 't',
7617                         ApplicationDelegate::eMenuID_ViewBacktrace)));
7618     view_menu_sp->AddSubmenu(
7619         MenuSP(new Menu("Registers", nullptr, 'r',
7620                         ApplicationDelegate::eMenuID_ViewRegisters)));
7621     view_menu_sp->AddSubmenu(MenuSP(new Menu(
7622         "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource)));
7623     view_menu_sp->AddSubmenu(
7624         MenuSP(new Menu("Variables", nullptr, 'v',
7625                         ApplicationDelegate::eMenuID_ViewVariables)));
7626     view_menu_sp->AddSubmenu(
7627         MenuSP(new Menu("Breakpoints", nullptr, 'b',
7628                         ApplicationDelegate::eMenuID_ViewBreakpoints)));
7629 
7630     MenuSP help_menu_sp(
7631         new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help));
7632     help_menu_sp->AddSubmenu(MenuSP(new Menu(
7633         "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp)));
7634 
7635     m_app_ap->Initialize();
7636     WindowSP &main_window_sp = m_app_ap->GetMainWindow();
7637 
7638     MenuSP menubar_sp(new Menu(Menu::Type::Bar));
7639     menubar_sp->AddSubmenu(lldb_menu_sp);
7640     menubar_sp->AddSubmenu(target_menu_sp);
7641     menubar_sp->AddSubmenu(process_menu_sp);
7642     menubar_sp->AddSubmenu(thread_menu_sp);
7643     menubar_sp->AddSubmenu(view_menu_sp);
7644     menubar_sp->AddSubmenu(help_menu_sp);
7645     menubar_sp->SetDelegate(app_menu_delegate_sp);
7646 
7647     Rect content_bounds = main_window_sp->GetFrame();
7648     Rect menubar_bounds = content_bounds.MakeMenuBar();
7649     Rect status_bounds = content_bounds.MakeStatusBar();
7650     Rect source_bounds;
7651     Rect variables_bounds;
7652     Rect threads_bounds;
7653     Rect source_variables_bounds;
7654     content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
7655                                            threads_bounds);
7656     source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds,
7657                                                       variables_bounds);
7658 
7659     WindowSP menubar_window_sp =
7660         main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false);
7661     // Let the menubar get keys if the active window doesn't handle the keys
7662     // that are typed so it can respond to menubar key presses.
7663     menubar_window_sp->SetCanBeActive(
7664         false); // Don't let the menubar become the active window
7665     menubar_window_sp->SetDelegate(menubar_sp);
7666 
7667     WindowSP source_window_sp(
7668         main_window_sp->CreateSubWindow("Source", source_bounds, true));
7669     WindowSP variables_window_sp(
7670         main_window_sp->CreateSubWindow("Variables", variables_bounds, false));
7671     WindowSP threads_window_sp(
7672         main_window_sp->CreateSubWindow("Threads", threads_bounds, false));
7673     WindowSP status_window_sp(
7674         main_window_sp->CreateSubWindow("Status", status_bounds, false));
7675     status_window_sp->SetCanBeActive(
7676         false); // Don't let the status bar become the active window
7677     main_window_sp->SetDelegate(
7678         std::static_pointer_cast<WindowDelegate>(app_delegate_sp));
7679     source_window_sp->SetDelegate(
7680         WindowDelegateSP(new SourceFileWindowDelegate(m_debugger)));
7681     variables_window_sp->SetDelegate(
7682         WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
7683     TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger));
7684     threads_window_sp->SetDelegate(WindowDelegateSP(
7685         new TreeWindowDelegate(m_debugger, thread_delegate_sp)));
7686     status_window_sp->SetDelegate(
7687         WindowDelegateSP(new StatusBarWindowDelegate(m_debugger)));
7688 
7689     // Show the main help window once the first time the curses GUI is
7690     // launched
7691     static bool g_showed_help = false;
7692     if (!g_showed_help) {
7693       g_showed_help = true;
7694       main_window_sp->CreateHelpSubwindow();
7695     }
7696 
7697     // All colors with black background.
7698     init_pair(1, COLOR_BLACK, COLOR_BLACK);
7699     init_pair(2, COLOR_RED, COLOR_BLACK);
7700     init_pair(3, COLOR_GREEN, COLOR_BLACK);
7701     init_pair(4, COLOR_YELLOW, COLOR_BLACK);
7702     init_pair(5, COLOR_BLUE, COLOR_BLACK);
7703     init_pair(6, COLOR_MAGENTA, COLOR_BLACK);
7704     init_pair(7, COLOR_CYAN, COLOR_BLACK);
7705     init_pair(8, COLOR_WHITE, COLOR_BLACK);
7706     // All colors with blue background.
7707     init_pair(9, COLOR_BLACK, COLOR_BLUE);
7708     init_pair(10, COLOR_RED, COLOR_BLUE);
7709     init_pair(11, COLOR_GREEN, COLOR_BLUE);
7710     init_pair(12, COLOR_YELLOW, COLOR_BLUE);
7711     init_pair(13, COLOR_BLUE, COLOR_BLUE);
7712     init_pair(14, COLOR_MAGENTA, COLOR_BLUE);
7713     init_pair(15, COLOR_CYAN, COLOR_BLUE);
7714     init_pair(16, COLOR_WHITE, COLOR_BLUE);
7715     // These must match the order in the color indexes enum.
7716     init_pair(17, COLOR_BLACK, COLOR_WHITE);
7717     init_pair(18, COLOR_MAGENTA, COLOR_WHITE);
7718     static_assert(LastColorPairIndex == 18, "Color indexes do not match.");
7719 
7720     define_key("\033[Z", KEY_SHIFT_TAB);
7721     define_key("\033\015", KEY_ALT_ENTER);
7722   }
7723 }
7724 
Deactivate()7725 void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); }
7726 
Run()7727 void IOHandlerCursesGUI::Run() {
7728   m_app_ap->Run(m_debugger);
7729   SetIsDone(true);
7730 }
7731 
7732 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default;
7733 
Cancel()7734 void IOHandlerCursesGUI::Cancel() {}
7735 
Interrupt()7736 bool IOHandlerCursesGUI::Interrupt() { return false; }
7737 
GotEOF()7738 void IOHandlerCursesGUI::GotEOF() {}
7739 
TerminalSizeChanged()7740 void IOHandlerCursesGUI::TerminalSizeChanged() {
7741   m_app_ap->TerminalSizeChanged();
7742 }
7743 
7744 #endif // LLDB_ENABLE_CURSES
7745