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