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