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     if (!m_need_to_exist)
1304       return;
1305 
1306     FileSpec file = GetResolvedFileSpec();
1307     if (!FileSystem::Instance().Exists(file)) {
1308       SetError("File doesn't exist!");
1309       return;
1310     }
1311     if (FileSystem::Instance().IsDirectory(file)) {
1312       SetError("Not a file!");
1313       return;
1314     }
1315   }
1316 
1317   FileSpec GetFileSpec() {
1318     FileSpec file_spec(GetPath());
1319     return file_spec;
1320   }
1321 
1322   FileSpec GetResolvedFileSpec() {
1323     FileSpec file_spec(GetPath());
1324     FileSystem::Instance().Resolve(file_spec);
1325     return file_spec;
1326   }
1327 
1328   const std::string &GetPath() { return m_content; }
1329 
1330 protected:
1331   bool m_need_to_exist;
1332 };
1333 
1334 class DirectoryFieldDelegate : public TextFieldDelegate {
1335 public:
1336   DirectoryFieldDelegate(const char *label, const char *content,
1337                          bool need_to_exist, bool required)
1338       : TextFieldDelegate(label, content, required),
1339         m_need_to_exist(need_to_exist) {}
1340 
1341   void FieldDelegateExitCallback() override {
1342     TextFieldDelegate::FieldDelegateExitCallback();
1343     if (!IsSpecified())
1344       return;
1345 
1346     if (!m_need_to_exist)
1347       return;
1348 
1349     FileSpec file = GetResolvedFileSpec();
1350     if (!FileSystem::Instance().Exists(file)) {
1351       SetError("Directory doesn't exist!");
1352       return;
1353     }
1354     if (!FileSystem::Instance().IsDirectory(file)) {
1355       SetError("Not a directory!");
1356       return;
1357     }
1358   }
1359 
1360   FileSpec GetFileSpec() {
1361     FileSpec file_spec(GetPath());
1362     return file_spec;
1363   }
1364 
1365   FileSpec GetResolvedFileSpec() {
1366     FileSpec file_spec(GetPath());
1367     FileSystem::Instance().Resolve(file_spec);
1368     return file_spec;
1369   }
1370 
1371   const std::string &GetPath() { return m_content; }
1372 
1373 protected:
1374   bool m_need_to_exist;
1375 };
1376 
1377 class BooleanFieldDelegate : public FieldDelegate {
1378 public:
1379   BooleanFieldDelegate(const char *label, bool content)
1380       : m_label(label), m_content(content) {}
1381 
1382   // Boolean fields are drawn as checkboxes.
1383   //
1384   // [X] Label  or [ ] Label
1385 
1386   // Boolean fields are have a single line.
1387   int FieldDelegateGetHeight() override { return 1; }
1388 
1389   void FieldDelegateDraw(SubPad &surface, bool is_selected) override {
1390     surface.MoveCursor(0, 0);
1391     surface.PutChar('[');
1392     if (is_selected)
1393       surface.AttributeOn(A_REVERSE);
1394     surface.PutChar(m_content ? ACS_DIAMOND : ' ');
1395     if (is_selected)
1396       surface.AttributeOff(A_REVERSE);
1397     surface.PutChar(']');
1398     surface.PutChar(' ');
1399     surface.PutCString(m_label.c_str());
1400   }
1401 
1402   void ToggleContent() { m_content = !m_content; }
1403 
1404   void SetContentToTrue() { m_content = true; }
1405 
1406   void SetContentToFalse() { m_content = false; }
1407 
1408   HandleCharResult FieldDelegateHandleChar(int key) override {
1409     switch (key) {
1410     case 't':
1411     case '1':
1412       SetContentToTrue();
1413       return eKeyHandled;
1414     case 'f':
1415     case '0':
1416       SetContentToFalse();
1417       return eKeyHandled;
1418     case ' ':
1419     case '\r':
1420     case '\n':
1421     case KEY_ENTER:
1422       ToggleContent();
1423       return eKeyHandled;
1424     default:
1425       break;
1426     }
1427     return eKeyNotHandled;
1428   }
1429 
1430   // Returns the boolean content of the field.
1431   bool GetBoolean() { return m_content; }
1432 
1433 protected:
1434   std::string m_label;
1435   bool m_content;
1436 };
1437 
1438 class ChoicesFieldDelegate : public FieldDelegate {
1439 public:
1440   ChoicesFieldDelegate(const char *label, int number_of_visible_choices,
1441                        std::vector<std::string> choices)
1442       : m_label(label), m_number_of_visible_choices(number_of_visible_choices),
1443         m_choices(choices), m_choice(0), m_first_visibile_choice(0) {}
1444 
1445   // Choices fields are drawn as titles boxses of a number of visible choices.
1446   // The rest of the choices become visible as the user scroll. The selected
1447   // choice is denoted by a diamond as the first character.
1448   //
1449   // __[Label]___________
1450   // |-Choice 1         |
1451   // | Choice 2         |
1452   // | Choice 3         |
1453   // |__________________|
1454 
1455   // Choices field have two border characters plus the number of visible
1456   // choices.
1457   int FieldDelegateGetHeight() override {
1458     return m_number_of_visible_choices + 2;
1459   }
1460 
1461   int GetNumberOfChoices() { return m_choices.size(); }
1462 
1463   // Get the index of the last visible choice.
1464   int GetLastVisibleChoice() {
1465     int index = m_first_visibile_choice + m_number_of_visible_choices;
1466     return std::min(index, GetNumberOfChoices()) - 1;
1467   }
1468 
1469   void DrawContent(SubPad &surface, bool is_selected) {
1470     int choices_to_draw = GetLastVisibleChoice() - m_first_visibile_choice + 1;
1471     for (int i = 0; i < choices_to_draw; i++) {
1472       surface.MoveCursor(0, i);
1473       int current_choice = m_first_visibile_choice + i;
1474       const char *text = m_choices[current_choice].c_str();
1475       bool highlight = is_selected && current_choice == m_choice;
1476       if (highlight)
1477         surface.AttributeOn(A_REVERSE);
1478       surface.PutChar(current_choice == m_choice ? ACS_DIAMOND : ' ');
1479       surface.PutCString(text);
1480       if (highlight)
1481         surface.AttributeOff(A_REVERSE);
1482     }
1483   }
1484 
1485   void FieldDelegateDraw(SubPad &surface, bool is_selected) override {
1486     UpdateScrolling();
1487 
1488     surface.TitledBox(m_label.c_str());
1489 
1490     Rect content_bounds = surface.GetFrame();
1491     content_bounds.Inset(1, 1);
1492     SubPad content_surface = SubPad(surface, content_bounds);
1493 
1494     DrawContent(content_surface, is_selected);
1495   }
1496 
1497   void SelectPrevious() {
1498     if (m_choice > 0)
1499       m_choice--;
1500   }
1501 
1502   void SelectNext() {
1503     if (m_choice < GetNumberOfChoices() - 1)
1504       m_choice++;
1505   }
1506 
1507   void UpdateScrolling() {
1508     if (m_choice > GetLastVisibleChoice()) {
1509       m_first_visibile_choice = m_choice - (m_number_of_visible_choices - 1);
1510       return;
1511     }
1512 
1513     if (m_choice < m_first_visibile_choice)
1514       m_first_visibile_choice = m_choice;
1515   }
1516 
1517   HandleCharResult FieldDelegateHandleChar(int key) override {
1518     switch (key) {
1519     case KEY_UP:
1520       SelectPrevious();
1521       return eKeyHandled;
1522     case KEY_DOWN:
1523       SelectNext();
1524       return eKeyHandled;
1525     default:
1526       break;
1527     }
1528     return eKeyNotHandled;
1529   }
1530 
1531   // Returns the content of the choice as a string.
1532   std::string GetChoiceContent() { return m_choices[m_choice]; }
1533 
1534   // Returns the index of the choice.
1535   int GetChoice() { return m_choice; }
1536 
1537   void SetChoice(const std::string &choice) {
1538     for (int i = 0; i < GetNumberOfChoices(); i++) {
1539       if (choice == m_choices[i]) {
1540         m_choice = i;
1541         return;
1542       }
1543     }
1544   }
1545 
1546 protected:
1547   std::string m_label;
1548   int m_number_of_visible_choices;
1549   std::vector<std::string> m_choices;
1550   // The index of the selected choice.
1551   int m_choice;
1552   // The index of the first visible choice in the field.
1553   int m_first_visibile_choice;
1554 };
1555 
1556 class PlatformPluginFieldDelegate : public ChoicesFieldDelegate {
1557 public:
1558   PlatformPluginFieldDelegate(Debugger &debugger)
1559       : ChoicesFieldDelegate("Platform Plugin", 3, GetPossiblePluginNames()) {
1560     PlatformSP platform_sp = debugger.GetPlatformList().GetSelectedPlatform();
1561     if (platform_sp)
1562       SetChoice(platform_sp->GetName().AsCString());
1563   }
1564 
1565   std::vector<std::string> GetPossiblePluginNames() {
1566     std::vector<std::string> names;
1567     size_t i = 0;
1568     while (auto name = PluginManager::GetPlatformPluginNameAtIndex(i++))
1569       names.push_back(name);
1570     return names;
1571   }
1572 
1573   std::string GetPluginName() {
1574     std::string plugin_name = GetChoiceContent();
1575     return plugin_name;
1576   }
1577 };
1578 
1579 class ProcessPluginFieldDelegate : public ChoicesFieldDelegate {
1580 public:
1581   ProcessPluginFieldDelegate()
1582       : ChoicesFieldDelegate("Process Plugin", 3, GetPossiblePluginNames()) {}
1583 
1584   std::vector<std::string> GetPossiblePluginNames() {
1585     std::vector<std::string> names;
1586     names.push_back("<default>");
1587 
1588     size_t i = 0;
1589     while (auto name = PluginManager::GetProcessPluginNameAtIndex(i++))
1590       names.push_back(name);
1591     return names;
1592   }
1593 
1594   std::string GetPluginName() {
1595     std::string plugin_name = GetChoiceContent();
1596     if (plugin_name == "<default>")
1597       return "";
1598     return plugin_name;
1599   }
1600 };
1601 
1602 template <class T> class ListFieldDelegate : public FieldDelegate {
1603 public:
1604   ListFieldDelegate(const char *label, T default_field)
1605       : m_label(label), m_default_field(default_field), m_selection_index(0),
1606         m_selection_type(SelectionType::NewButton) {}
1607 
1608   // Signify which element is selected. If a field or a remove button is
1609   // selected, then m_selection_index signifies the particular field that
1610   // is selected or the field that the remove button belongs to.
1611   enum class SelectionType { Field, RemoveButton, NewButton };
1612 
1613   // A List field is drawn as a titled box of a number of other fields of the
1614   // same type. Each field has a Remove button next to it that removes the
1615   // corresponding field. Finally, the last line contains a New button to add a
1616   // new field.
1617   //
1618   // __[Label]___________
1619   // | Field 0 [Remove] |
1620   // | Field 1 [Remove] |
1621   // | Field 2 [Remove] |
1622   // |      [New]       |
1623   // |__________________|
1624 
1625   // List fields have two lines for border characters, 1 line for the New
1626   // button, and the total height of the available fields.
1627   int FieldDelegateGetHeight() override {
1628     // 2 border characters.
1629     int height = 2;
1630     // Total height of the fields.
1631     for (int i = 0; i < GetNumberOfFields(); i++) {
1632       height += m_fields[i].FieldDelegateGetHeight();
1633     }
1634     // A line for the New button.
1635     height++;
1636     return height;
1637   }
1638 
1639   ScrollContext FieldDelegateGetScrollContext() override {
1640     int height = FieldDelegateGetHeight();
1641     if (m_selection_type == SelectionType::NewButton)
1642       return ScrollContext(height - 2, height - 1);
1643 
1644     FieldDelegate &field = m_fields[m_selection_index];
1645     ScrollContext context = field.FieldDelegateGetScrollContext();
1646 
1647     // Start at 1 because of the top border.
1648     int offset = 1;
1649     for (int i = 0; i < m_selection_index; i++) {
1650       offset += m_fields[i].FieldDelegateGetHeight();
1651     }
1652     context.Offset(offset);
1653 
1654     // If the scroll context is touching the top border, include it in the
1655     // context to show the label.
1656     if (context.start == 1)
1657       context.start--;
1658 
1659     // If the scroll context is touching the new button, include it as well as
1660     // the bottom border in the context.
1661     if (context.end == height - 3)
1662       context.end += 2;
1663 
1664     return context;
1665   }
1666 
1667   void DrawRemoveButton(SubPad &surface, int highlight) {
1668     surface.MoveCursor(1, surface.GetHeight() / 2);
1669     if (highlight)
1670       surface.AttributeOn(A_REVERSE);
1671     surface.PutCString("[Remove]");
1672     if (highlight)
1673       surface.AttributeOff(A_REVERSE);
1674   }
1675 
1676   void DrawFields(SubPad &surface, bool is_selected) {
1677     int line = 0;
1678     int width = surface.GetWidth();
1679     for (int i = 0; i < GetNumberOfFields(); i++) {
1680       int height = m_fields[i].FieldDelegateGetHeight();
1681       Rect bounds = Rect(Point(0, line), Size(width, height));
1682       Rect field_bounds, remove_button_bounds;
1683       bounds.VerticalSplit(bounds.size.width - sizeof(" [Remove]"),
1684                            field_bounds, remove_button_bounds);
1685       SubPad field_surface = SubPad(surface, field_bounds);
1686       SubPad remove_button_surface = SubPad(surface, remove_button_bounds);
1687 
1688       bool is_element_selected = m_selection_index == i && is_selected;
1689       bool is_field_selected =
1690           is_element_selected && m_selection_type == SelectionType::Field;
1691       bool is_remove_button_selected =
1692           is_element_selected &&
1693           m_selection_type == SelectionType::RemoveButton;
1694       m_fields[i].FieldDelegateDraw(field_surface, is_field_selected);
1695       DrawRemoveButton(remove_button_surface, is_remove_button_selected);
1696 
1697       line += height;
1698     }
1699   }
1700 
1701   void DrawNewButton(SubPad &surface, bool is_selected) {
1702     const char *button_text = "[New]";
1703     int x = (surface.GetWidth() - sizeof(button_text) - 1) / 2;
1704     surface.MoveCursor(x, 0);
1705     bool highlight =
1706         is_selected && m_selection_type == SelectionType::NewButton;
1707     if (highlight)
1708       surface.AttributeOn(A_REVERSE);
1709     surface.PutCString(button_text);
1710     if (highlight)
1711       surface.AttributeOff(A_REVERSE);
1712   }
1713 
1714   void FieldDelegateDraw(SubPad &surface, bool is_selected) override {
1715     surface.TitledBox(m_label.c_str());
1716 
1717     Rect content_bounds = surface.GetFrame();
1718     content_bounds.Inset(1, 1);
1719     Rect fields_bounds, new_button_bounds;
1720     content_bounds.HorizontalSplit(content_bounds.size.height - 1,
1721                                    fields_bounds, new_button_bounds);
1722     SubPad fields_surface = SubPad(surface, fields_bounds);
1723     SubPad new_button_surface = SubPad(surface, new_button_bounds);
1724 
1725     DrawFields(fields_surface, is_selected);
1726     DrawNewButton(new_button_surface, is_selected);
1727   }
1728 
1729   void AddNewField() {
1730     m_fields.push_back(m_default_field);
1731     m_selection_index = GetNumberOfFields() - 1;
1732     m_selection_type = SelectionType::Field;
1733     FieldDelegate &field = m_fields[m_selection_index];
1734     field.FieldDelegateSelectFirstElement();
1735   }
1736 
1737   void RemoveField() {
1738     m_fields.erase(m_fields.begin() + m_selection_index);
1739     if (m_selection_index != 0)
1740       m_selection_index--;
1741 
1742     if (GetNumberOfFields() > 0) {
1743       m_selection_type = SelectionType::Field;
1744       FieldDelegate &field = m_fields[m_selection_index];
1745       field.FieldDelegateSelectFirstElement();
1746     } else
1747       m_selection_type = SelectionType::NewButton;
1748   }
1749 
1750   HandleCharResult SelectNext(int key) {
1751     if (m_selection_type == SelectionType::NewButton)
1752       return eKeyNotHandled;
1753 
1754     if (m_selection_type == SelectionType::RemoveButton) {
1755       if (m_selection_index == GetNumberOfFields() - 1) {
1756         m_selection_type = SelectionType::NewButton;
1757         return eKeyHandled;
1758       }
1759       m_selection_index++;
1760       m_selection_type = SelectionType::Field;
1761       FieldDelegate &next_field = m_fields[m_selection_index];
1762       next_field.FieldDelegateSelectFirstElement();
1763       return eKeyHandled;
1764     }
1765 
1766     FieldDelegate &field = m_fields[m_selection_index];
1767     if (!field.FieldDelegateOnLastOrOnlyElement()) {
1768       return field.FieldDelegateHandleChar(key);
1769     }
1770 
1771     field.FieldDelegateExitCallback();
1772 
1773     m_selection_type = SelectionType::RemoveButton;
1774     return eKeyHandled;
1775   }
1776 
1777   HandleCharResult SelectPrevious(int key) {
1778     if (FieldDelegateOnFirstOrOnlyElement())
1779       return eKeyNotHandled;
1780 
1781     if (m_selection_type == SelectionType::RemoveButton) {
1782       m_selection_type = SelectionType::Field;
1783       FieldDelegate &field = m_fields[m_selection_index];
1784       field.FieldDelegateSelectLastElement();
1785       return eKeyHandled;
1786     }
1787 
1788     if (m_selection_type == SelectionType::NewButton) {
1789       m_selection_type = SelectionType::RemoveButton;
1790       m_selection_index = GetNumberOfFields() - 1;
1791       return eKeyHandled;
1792     }
1793 
1794     FieldDelegate &field = m_fields[m_selection_index];
1795     if (!field.FieldDelegateOnFirstOrOnlyElement()) {
1796       return field.FieldDelegateHandleChar(key);
1797     }
1798 
1799     field.FieldDelegateExitCallback();
1800 
1801     m_selection_type = SelectionType::RemoveButton;
1802     m_selection_index--;
1803     return eKeyHandled;
1804   }
1805 
1806   HandleCharResult FieldDelegateHandleChar(int key) override {
1807     switch (key) {
1808     case '\r':
1809     case '\n':
1810     case KEY_ENTER:
1811       switch (m_selection_type) {
1812       case SelectionType::NewButton:
1813         AddNewField();
1814         return eKeyHandled;
1815       case SelectionType::RemoveButton:
1816         RemoveField();
1817         return eKeyHandled;
1818       default:
1819         break;
1820       }
1821       break;
1822     case '\t':
1823       SelectNext(key);
1824       return eKeyHandled;
1825     case KEY_SHIFT_TAB:
1826       SelectPrevious(key);
1827       return eKeyHandled;
1828     default:
1829       break;
1830     }
1831 
1832     // If the key wasn't handled and one of the fields is selected, pass the key
1833     // to that field.
1834     if (m_selection_type == SelectionType::Field) {
1835       return m_fields[m_selection_index].FieldDelegateHandleChar(key);
1836     }
1837 
1838     return eKeyNotHandled;
1839   }
1840 
1841   bool FieldDelegateOnLastOrOnlyElement() override {
1842     if (m_selection_type == SelectionType::NewButton) {
1843       return true;
1844     }
1845     return false;
1846   }
1847 
1848   bool FieldDelegateOnFirstOrOnlyElement() override {
1849     if (m_selection_type == SelectionType::NewButton &&
1850         GetNumberOfFields() == 0)
1851       return true;
1852 
1853     if (m_selection_type == SelectionType::Field && m_selection_index == 0) {
1854       FieldDelegate &field = m_fields[m_selection_index];
1855       return field.FieldDelegateOnFirstOrOnlyElement();
1856     }
1857 
1858     return false;
1859   }
1860 
1861   void FieldDelegateSelectFirstElement() override {
1862     if (GetNumberOfFields() == 0) {
1863       m_selection_type = SelectionType::NewButton;
1864       return;
1865     }
1866 
1867     m_selection_type = SelectionType::Field;
1868     m_selection_index = 0;
1869   }
1870 
1871   void FieldDelegateSelectLastElement() override {
1872     m_selection_type = SelectionType::NewButton;
1873     return;
1874   }
1875 
1876   int GetNumberOfFields() { return m_fields.size(); }
1877 
1878   // Returns the form delegate at the current index.
1879   T &GetField(int index) { return m_fields[index]; }
1880 
1881 protected:
1882   std::string m_label;
1883   // The default field delegate instance from which new field delegates will be
1884   // created though a copy.
1885   T m_default_field;
1886   std::vector<T> m_fields;
1887   int m_selection_index;
1888   // See SelectionType class enum.
1889   SelectionType m_selection_type;
1890 };
1891 
1892 class FormAction {
1893 public:
1894   FormAction(const char *label, std::function<void(Window &)> action)
1895       : m_action(action) {
1896     if (label)
1897       m_label = label;
1898   }
1899 
1900   // Draw a centered [Label].
1901   void Draw(SubPad &surface, bool is_selected) {
1902     int x = (surface.GetWidth() - m_label.length()) / 2;
1903     surface.MoveCursor(x, 0);
1904     if (is_selected)
1905       surface.AttributeOn(A_REVERSE);
1906     surface.PutChar('[');
1907     surface.PutCString(m_label.c_str());
1908     surface.PutChar(']');
1909     if (is_selected)
1910       surface.AttributeOff(A_REVERSE);
1911   }
1912 
1913   void Execute(Window &window) { m_action(window); }
1914 
1915   const std::string &GetLabel() { return m_label; }
1916 
1917 protected:
1918   std::string m_label;
1919   std::function<void(Window &)> m_action;
1920 };
1921 
1922 class FormDelegate {
1923 public:
1924   FormDelegate() {}
1925 
1926   virtual ~FormDelegate() = default;
1927 
1928   virtual std::string GetName() = 0;
1929 
1930   virtual void UpdateFieldsVisibility() { return; }
1931 
1932   FieldDelegate *GetField(uint32_t field_index) {
1933     if (field_index < m_fields.size())
1934       return m_fields[field_index].get();
1935     return nullptr;
1936   }
1937 
1938   FormAction &GetAction(int action_index) { return m_actions[action_index]; }
1939 
1940   int GetNumberOfFields() { return m_fields.size(); }
1941 
1942   int GetNumberOfActions() { return m_actions.size(); }
1943 
1944   bool HasError() { return !m_error.empty(); }
1945 
1946   void ClearError() { m_error.clear(); }
1947 
1948   const std::string &GetError() { return m_error; }
1949 
1950   void SetError(const char *error) { m_error = error; }
1951 
1952   // If all fields are valid, true is returned. Otherwise, an error message is
1953   // set and false is returned. This method is usually called at the start of an
1954   // action that requires valid fields.
1955   bool CheckFieldsValidity() {
1956     for (int i = 0; i < GetNumberOfFields(); i++) {
1957       if (GetField(i)->FieldDelegateHasError()) {
1958         SetError("Some fields are invalid!");
1959         return false;
1960       }
1961     }
1962     return true;
1963   }
1964 
1965   // Factory methods to create and add fields of specific types.
1966 
1967   TextFieldDelegate *AddTextField(const char *label, const char *content,
1968                                   bool required) {
1969     TextFieldDelegate *delegate =
1970         new TextFieldDelegate(label, content, required);
1971     m_fields.push_back(FieldDelegateUP(delegate));
1972     return delegate;
1973   }
1974 
1975   FileFieldDelegate *AddFileField(const char *label, const char *content,
1976                                   bool need_to_exist, bool required) {
1977     FileFieldDelegate *delegate =
1978         new FileFieldDelegate(label, content, need_to_exist, required);
1979     m_fields.push_back(FieldDelegateUP(delegate));
1980     return delegate;
1981   }
1982 
1983   DirectoryFieldDelegate *AddDirectoryField(const char *label,
1984                                             const char *content,
1985                                             bool need_to_exist, bool required) {
1986     DirectoryFieldDelegate *delegate =
1987         new DirectoryFieldDelegate(label, content, need_to_exist, required);
1988     m_fields.push_back(FieldDelegateUP(delegate));
1989     return delegate;
1990   }
1991 
1992   IntegerFieldDelegate *AddIntegerField(const char *label, int content,
1993                                         bool required) {
1994     IntegerFieldDelegate *delegate =
1995         new IntegerFieldDelegate(label, content, required);
1996     m_fields.push_back(FieldDelegateUP(delegate));
1997     return delegate;
1998   }
1999 
2000   BooleanFieldDelegate *AddBooleanField(const char *label, bool content) {
2001     BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content);
2002     m_fields.push_back(FieldDelegateUP(delegate));
2003     return delegate;
2004   }
2005 
2006   ChoicesFieldDelegate *AddChoicesField(const char *label, int height,
2007                                         std::vector<std::string> choices) {
2008     ChoicesFieldDelegate *delegate =
2009         new ChoicesFieldDelegate(label, height, choices);
2010     m_fields.push_back(FieldDelegateUP(delegate));
2011     return delegate;
2012   }
2013 
2014   PlatformPluginFieldDelegate *AddPlatformPluginField(Debugger &debugger) {
2015     PlatformPluginFieldDelegate *delegate =
2016         new PlatformPluginFieldDelegate(debugger);
2017     m_fields.push_back(FieldDelegateUP(delegate));
2018     return delegate;
2019   }
2020 
2021   ProcessPluginFieldDelegate *AddProcessPluginField() {
2022     ProcessPluginFieldDelegate *delegate = new ProcessPluginFieldDelegate();
2023     m_fields.push_back(FieldDelegateUP(delegate));
2024     return delegate;
2025   }
2026 
2027   template <class T>
2028   ListFieldDelegate<T> *AddListField(const char *label, T default_field) {
2029     ListFieldDelegate<T> *delegate =
2030         new ListFieldDelegate<T>(label, default_field);
2031     m_fields.push_back(FieldDelegateUP(delegate));
2032     return delegate;
2033   }
2034 
2035   // Factory methods for adding actions.
2036 
2037   void AddAction(const char *label, std::function<void(Window &)> action) {
2038     m_actions.push_back(FormAction(label, action));
2039   }
2040 
2041 protected:
2042   std::vector<FieldDelegateUP> m_fields;
2043   std::vector<FormAction> m_actions;
2044   // Optional error message. If empty, form is considered to have no error.
2045   std::string m_error;
2046 };
2047 
2048 typedef std::shared_ptr<FormDelegate> FormDelegateSP;
2049 
2050 class FormWindowDelegate : public WindowDelegate {
2051 public:
2052   FormWindowDelegate(FormDelegateSP &delegate_sp)
2053       : m_delegate_sp(delegate_sp), m_selection_index(0),
2054         m_first_visible_line(0) {
2055     assert(m_delegate_sp->GetNumberOfActions() > 0);
2056     if (m_delegate_sp->GetNumberOfFields() > 0)
2057       m_selection_type = SelectionType::Field;
2058     else
2059       m_selection_type = SelectionType::Action;
2060   }
2061 
2062   // Signify which element is selected. If a field or an action is selected,
2063   // then m_selection_index signifies the particular field or action that is
2064   // selected.
2065   enum class SelectionType { Field, Action };
2066 
2067   // A form window is padded by one character from all sides. First, if an error
2068   // message exists, it is drawn followed by a separator. Then one or more
2069   // fields are drawn. Finally, all available actions are drawn on a single
2070   // line.
2071   //
2072   // ___<Form Name>_________________________________________________
2073   // |                                                             |
2074   // | - Error message if it exists.                               |
2075   // |-------------------------------------------------------------|
2076   // | Form elements here.                                         |
2077   // |                       Form actions here.                    |
2078   // |                                                             |
2079   // |______________________________________[Press Esc to cancel]__|
2080   //
2081 
2082   // One line for the error and another for the horizontal line.
2083   int GetErrorHeight() {
2084     if (m_delegate_sp->HasError())
2085       return 2;
2086     return 0;
2087   }
2088 
2089   // Actions span a single line.
2090   int GetActionsHeight() {
2091     if (m_delegate_sp->GetNumberOfActions() > 0)
2092       return 1;
2093     return 0;
2094   }
2095 
2096   // Get the total number of needed lines to draw the contents.
2097   int GetContentHeight() {
2098     int height = 0;
2099     height += GetErrorHeight();
2100     for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2101       if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2102         continue;
2103       height += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2104     }
2105     height += GetActionsHeight();
2106     return height;
2107   }
2108 
2109   ScrollContext GetScrollContext() {
2110     if (m_selection_type == SelectionType::Action)
2111       return ScrollContext(GetContentHeight() - 1);
2112 
2113     FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2114     ScrollContext context = field->FieldDelegateGetScrollContext();
2115 
2116     int offset = GetErrorHeight();
2117     for (int i = 0; i < m_selection_index; i++) {
2118       if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2119         continue;
2120       offset += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2121     }
2122     context.Offset(offset);
2123 
2124     // If the context is touching the error, include the error in the context as
2125     // well.
2126     if (context.start == GetErrorHeight())
2127       context.start = 0;
2128 
2129     return context;
2130   }
2131 
2132   void UpdateScrolling(DerivedWindow &surface) {
2133     ScrollContext context = GetScrollContext();
2134     int content_height = GetContentHeight();
2135     int surface_height = surface.GetHeight();
2136     int visible_height = std::min(content_height, surface_height);
2137     int last_visible_line = m_first_visible_line + visible_height - 1;
2138 
2139     // If the last visible line is bigger than the content, then it is invalid
2140     // and needs to be set to the last line in the content. This can happen when
2141     // a field has shrunk in height.
2142     if (last_visible_line > content_height - 1) {
2143       m_first_visible_line = content_height - visible_height;
2144     }
2145 
2146     if (context.start < m_first_visible_line) {
2147       m_first_visible_line = context.start;
2148       return;
2149     }
2150 
2151     if (context.end > last_visible_line) {
2152       m_first_visible_line = context.end - visible_height + 1;
2153     }
2154   }
2155 
2156   void DrawError(SubPad &surface) {
2157     if (!m_delegate_sp->HasError())
2158       return;
2159     surface.MoveCursor(0, 0);
2160     surface.AttributeOn(COLOR_PAIR(RedOnBlack));
2161     surface.PutChar(ACS_DIAMOND);
2162     surface.PutChar(' ');
2163     surface.PutCStringTruncated(1, m_delegate_sp->GetError().c_str());
2164     surface.AttributeOff(COLOR_PAIR(RedOnBlack));
2165 
2166     surface.MoveCursor(0, 1);
2167     surface.HorizontalLine(surface.GetWidth());
2168   }
2169 
2170   void DrawFields(SubPad &surface) {
2171     int line = 0;
2172     int width = surface.GetWidth();
2173     bool a_field_is_selected = m_selection_type == SelectionType::Field;
2174     for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2175       FieldDelegate *field = m_delegate_sp->GetField(i);
2176       if (!field->FieldDelegateIsVisible())
2177         continue;
2178       bool is_field_selected = a_field_is_selected && m_selection_index == i;
2179       int height = field->FieldDelegateGetHeight();
2180       Rect bounds = Rect(Point(0, line), Size(width, height));
2181       SubPad field_surface = SubPad(surface, bounds);
2182       field->FieldDelegateDraw(field_surface, is_field_selected);
2183       line += height;
2184     }
2185   }
2186 
2187   void DrawActions(SubPad &surface) {
2188     int number_of_actions = m_delegate_sp->GetNumberOfActions();
2189     int width = surface.GetWidth() / number_of_actions;
2190     bool an_action_is_selected = m_selection_type == SelectionType::Action;
2191     int x = 0;
2192     for (int i = 0; i < number_of_actions; i++) {
2193       bool is_action_selected = an_action_is_selected && m_selection_index == i;
2194       FormAction &action = m_delegate_sp->GetAction(i);
2195       Rect bounds = Rect(Point(x, 0), Size(width, 1));
2196       SubPad action_surface = SubPad(surface, bounds);
2197       action.Draw(action_surface, is_action_selected);
2198       x += width;
2199     }
2200   }
2201 
2202   void DrawElements(SubPad &surface) {
2203     Rect frame = surface.GetFrame();
2204     Rect fields_bounds, actions_bounds;
2205     frame.HorizontalSplit(surface.GetHeight() - GetActionsHeight(),
2206                           fields_bounds, actions_bounds);
2207     SubPad fields_surface = SubPad(surface, fields_bounds);
2208     SubPad actions_surface = SubPad(surface, actions_bounds);
2209 
2210     DrawFields(fields_surface);
2211     DrawActions(actions_surface);
2212   }
2213 
2214   // Contents are first drawn on a pad. Then a subset of that pad is copied to
2215   // the derived window starting at the first visible line. This essentially
2216   // provides scrolling functionality.
2217   void DrawContent(DerivedWindow &surface) {
2218     UpdateScrolling(surface);
2219 
2220     int width = surface.GetWidth();
2221     int height = GetContentHeight();
2222     Pad pad = Pad(Size(width, height));
2223 
2224     Rect frame = pad.GetFrame();
2225     Rect error_bounds, elements_bounds;
2226     frame.HorizontalSplit(GetErrorHeight(), error_bounds, elements_bounds);
2227     SubPad error_surface = SubPad(pad, error_bounds);
2228     SubPad elements_surface = SubPad(pad, elements_bounds);
2229 
2230     DrawError(error_surface);
2231     DrawElements(elements_surface);
2232 
2233     int copy_height = std::min(surface.GetHeight(), pad.GetHeight());
2234     pad.CopyToSurface(surface, Point(0, m_first_visible_line), Point(),
2235                       Size(width, copy_height));
2236   }
2237 
2238   bool WindowDelegateDraw(Window &window, bool force) override {
2239     m_delegate_sp->UpdateFieldsVisibility();
2240 
2241     window.Erase();
2242 
2243     window.DrawTitleBox(m_delegate_sp->GetName().c_str(),
2244                         "Press Esc to cancel");
2245 
2246     Rect content_bounds = window.GetFrame();
2247     content_bounds.Inset(2, 2);
2248     DerivedWindow content_surface = DerivedWindow(window, content_bounds);
2249 
2250     DrawContent(content_surface);
2251     return true;
2252   }
2253 
2254   void SkipNextHiddenFields() {
2255     while (true) {
2256       if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2257         return;
2258 
2259       if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2260         m_selection_type = SelectionType::Action;
2261         m_selection_index = 0;
2262         return;
2263       }
2264 
2265       m_selection_index++;
2266     }
2267   }
2268 
2269   HandleCharResult SelectNext(int key) {
2270     if (m_selection_type == SelectionType::Action) {
2271       if (m_selection_index < m_delegate_sp->GetNumberOfActions() - 1) {
2272         m_selection_index++;
2273         return eKeyHandled;
2274       }
2275 
2276       m_selection_index = 0;
2277       m_selection_type = SelectionType::Field;
2278       SkipNextHiddenFields();
2279       if (m_selection_type == SelectionType::Field) {
2280         FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2281         next_field->FieldDelegateSelectFirstElement();
2282       }
2283       return eKeyHandled;
2284     }
2285 
2286     FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2287     if (!field->FieldDelegateOnLastOrOnlyElement()) {
2288       return field->FieldDelegateHandleChar(key);
2289     }
2290 
2291     field->FieldDelegateExitCallback();
2292 
2293     if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2294       m_selection_type = SelectionType::Action;
2295       m_selection_index = 0;
2296       return eKeyHandled;
2297     }
2298 
2299     m_selection_index++;
2300     SkipNextHiddenFields();
2301 
2302     if (m_selection_type == SelectionType::Field) {
2303       FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2304       next_field->FieldDelegateSelectFirstElement();
2305     }
2306 
2307     return eKeyHandled;
2308   }
2309 
2310   void SkipPreviousHiddenFields() {
2311     while (true) {
2312       if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2313         return;
2314 
2315       if (m_selection_index == 0) {
2316         m_selection_type = SelectionType::Action;
2317         m_selection_index = 0;
2318         return;
2319       }
2320 
2321       m_selection_index--;
2322     }
2323   }
2324 
2325   HandleCharResult SelectPrevious(int key) {
2326     if (m_selection_type == SelectionType::Action) {
2327       if (m_selection_index > 0) {
2328         m_selection_index--;
2329         return eKeyHandled;
2330       }
2331       m_selection_index = m_delegate_sp->GetNumberOfFields() - 1;
2332       m_selection_type = SelectionType::Field;
2333       SkipPreviousHiddenFields();
2334       if (m_selection_type == SelectionType::Field) {
2335         FieldDelegate *previous_field =
2336             m_delegate_sp->GetField(m_selection_index);
2337         previous_field->FieldDelegateSelectLastElement();
2338       }
2339       return eKeyHandled;
2340     }
2341 
2342     FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2343     if (!field->FieldDelegateOnFirstOrOnlyElement()) {
2344       return field->FieldDelegateHandleChar(key);
2345     }
2346 
2347     field->FieldDelegateExitCallback();
2348 
2349     if (m_selection_index == 0) {
2350       m_selection_type = SelectionType::Action;
2351       m_selection_index = m_delegate_sp->GetNumberOfActions() - 1;
2352       return eKeyHandled;
2353     }
2354 
2355     m_selection_index--;
2356     SkipPreviousHiddenFields();
2357 
2358     if (m_selection_type == SelectionType::Field) {
2359       FieldDelegate *previous_field =
2360           m_delegate_sp->GetField(m_selection_index);
2361       previous_field->FieldDelegateSelectLastElement();
2362     }
2363 
2364     return eKeyHandled;
2365   }
2366 
2367   void ExecuteAction(Window &window) {
2368     FormAction &action = m_delegate_sp->GetAction(m_selection_index);
2369     action.Execute(window);
2370     if (m_delegate_sp->HasError()) {
2371       m_first_visible_line = 0;
2372       m_selection_index = 0;
2373       m_selection_type = SelectionType::Field;
2374     }
2375   }
2376 
2377   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
2378     switch (key) {
2379     case '\r':
2380     case '\n':
2381     case KEY_ENTER:
2382       if (m_selection_type == SelectionType::Action) {
2383         ExecuteAction(window);
2384         return eKeyHandled;
2385       }
2386       break;
2387     case '\t':
2388       return SelectNext(key);
2389     case KEY_SHIFT_TAB:
2390       return SelectPrevious(key);
2391     case KEY_ESCAPE:
2392       window.GetParent()->RemoveSubWindow(&window);
2393       return eKeyHandled;
2394     default:
2395       break;
2396     }
2397 
2398     // If the key wasn't handled and one of the fields is selected, pass the key
2399     // to that field.
2400     if (m_selection_type == SelectionType::Field) {
2401       FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2402       return field->FieldDelegateHandleChar(key);
2403     }
2404 
2405     return eKeyNotHandled;
2406   }
2407 
2408 protected:
2409   FormDelegateSP m_delegate_sp;
2410   // The index of the currently selected SelectionType.
2411   int m_selection_index;
2412   // See SelectionType class enum.
2413   SelectionType m_selection_type;
2414   // The first visible line from the pad.
2415   int m_first_visible_line;
2416 };
2417 
2418 ///////////////////////////
2419 // Form Delegate Instances
2420 ///////////////////////////
2421 
2422 class DetachOrKillProcessFormDelegate : public FormDelegate {
2423 public:
2424   DetachOrKillProcessFormDelegate(Process *process) : m_process(process) {
2425     SetError("There is a running process, either detach or kill it.");
2426 
2427     m_keep_stopped_field =
2428         AddBooleanField("Keep process stopped when detaching.", false);
2429 
2430     AddAction("Detach", [this](Window &window) { Detach(window); });
2431     AddAction("Kill", [this](Window &window) { Kill(window); });
2432   }
2433 
2434   std::string GetName() override { return "Detach/Kill Process"; }
2435 
2436   void Kill(Window &window) {
2437     Status destroy_status(m_process->Destroy(false));
2438     if (destroy_status.Fail()) {
2439       SetError("Failed to kill process.");
2440       return;
2441     }
2442     window.GetParent()->RemoveSubWindow(&window);
2443   }
2444 
2445   void Detach(Window &window) {
2446     Status detach_status(m_process->Detach(m_keep_stopped_field->GetBoolean()));
2447     if (detach_status.Fail()) {
2448       SetError("Failed to detach from process.");
2449       return;
2450     }
2451     window.GetParent()->RemoveSubWindow(&window);
2452   }
2453 
2454 protected:
2455   Process *m_process;
2456   BooleanFieldDelegate *m_keep_stopped_field;
2457 };
2458 
2459 class ProcessAttachFormDelegate : public FormDelegate {
2460 public:
2461   ProcessAttachFormDelegate(Debugger &debugger, WindowSP main_window_sp)
2462       : m_debugger(debugger), m_main_window_sp(main_window_sp) {
2463     std::vector<std::string> types;
2464     types.push_back(std::string("Name"));
2465     types.push_back(std::string("PID"));
2466     m_type_field = AddChoicesField("Attach By", 2, types);
2467     m_pid_field = AddIntegerField("PID", 0, true);
2468     m_name_field =
2469         AddTextField("Process Name", GetDefaultProcessName().c_str(), true);
2470     m_continue_field = AddBooleanField("Continue once attached.", false);
2471     m_wait_for_field = AddBooleanField("Wait for process to launch.", false);
2472     m_include_existing_field =
2473         AddBooleanField("Include existing processes.", false);
2474     m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
2475     m_plugin_field = AddProcessPluginField();
2476 
2477     AddAction("Attach", [this](Window &window) { Attach(window); });
2478   }
2479 
2480   std::string GetName() override { return "Attach Process"; }
2481 
2482   void UpdateFieldsVisibility() override {
2483     if (m_type_field->GetChoiceContent() == "Name") {
2484       m_pid_field->FieldDelegateHide();
2485       m_name_field->FieldDelegateShow();
2486       m_wait_for_field->FieldDelegateShow();
2487       if (m_wait_for_field->GetBoolean())
2488         m_include_existing_field->FieldDelegateShow();
2489       else
2490         m_include_existing_field->FieldDelegateHide();
2491     } else {
2492       m_pid_field->FieldDelegateShow();
2493       m_name_field->FieldDelegateHide();
2494       m_wait_for_field->FieldDelegateHide();
2495       m_include_existing_field->FieldDelegateHide();
2496     }
2497     if (m_show_advanced_field->GetBoolean())
2498       m_plugin_field->FieldDelegateShow();
2499     else
2500       m_plugin_field->FieldDelegateHide();
2501   }
2502 
2503   // Get the basename of the target's main executable if available, empty string
2504   // otherwise.
2505   std::string GetDefaultProcessName() {
2506     Target *target = m_debugger.GetSelectedTarget().get();
2507     if (target == nullptr)
2508       return "";
2509 
2510     ModuleSP module_sp = target->GetExecutableModule();
2511     if (!module_sp->IsExecutable())
2512       return "";
2513 
2514     return module_sp->GetFileSpec().GetFilename().AsCString();
2515   }
2516 
2517   bool StopRunningProcess() {
2518     ExecutionContext exe_ctx =
2519         m_debugger.GetCommandInterpreter().GetExecutionContext();
2520 
2521     if (!exe_ctx.HasProcessScope())
2522       return false;
2523 
2524     Process *process = exe_ctx.GetProcessPtr();
2525     if (!(process && process->IsAlive()))
2526       return false;
2527 
2528     FormDelegateSP form_delegate_sp =
2529         FormDelegateSP(new DetachOrKillProcessFormDelegate(process));
2530     Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);
2531     WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(
2532         form_delegate_sp->GetName().c_str(), bounds, true);
2533     WindowDelegateSP window_delegate_sp =
2534         WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
2535     form_window_sp->SetDelegate(window_delegate_sp);
2536 
2537     return true;
2538   }
2539 
2540   Target *GetTarget() {
2541     Target *target = m_debugger.GetSelectedTarget().get();
2542 
2543     if (target != nullptr)
2544       return target;
2545 
2546     TargetSP new_target_sp;
2547     m_debugger.GetTargetList().CreateTarget(
2548         m_debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp);
2549 
2550     target = new_target_sp.get();
2551 
2552     if (target == nullptr)
2553       SetError("Failed to create target.");
2554 
2555     m_debugger.GetTargetList().SetSelectedTarget(new_target_sp);
2556 
2557     return target;
2558   }
2559 
2560   ProcessAttachInfo GetAttachInfo() {
2561     ProcessAttachInfo attach_info;
2562     attach_info.SetContinueOnceAttached(m_continue_field->GetBoolean());
2563     if (m_type_field->GetChoiceContent() == "Name") {
2564       attach_info.GetExecutableFile().SetFile(m_name_field->GetText(),
2565                                               FileSpec::Style::native);
2566       attach_info.SetWaitForLaunch(m_wait_for_field->GetBoolean());
2567       if (m_wait_for_field->GetBoolean())
2568         attach_info.SetIgnoreExisting(!m_include_existing_field->GetBoolean());
2569     } else {
2570       attach_info.SetProcessID(m_pid_field->GetInteger());
2571     }
2572     attach_info.SetProcessPluginName(m_plugin_field->GetPluginName());
2573 
2574     return attach_info;
2575   }
2576 
2577   void Attach(Window &window) {
2578     ClearError();
2579 
2580     bool all_fields_are_valid = CheckFieldsValidity();
2581     if (!all_fields_are_valid)
2582       return;
2583 
2584     bool process_is_running = StopRunningProcess();
2585     if (process_is_running)
2586       return;
2587 
2588     Target *target = GetTarget();
2589     if (HasError())
2590       return;
2591 
2592     StreamString stream;
2593     ProcessAttachInfo attach_info = GetAttachInfo();
2594     Status status = target->Attach(attach_info, &stream);
2595 
2596     if (status.Fail()) {
2597       SetError(status.AsCString());
2598       return;
2599     }
2600 
2601     ProcessSP process_sp(target->GetProcessSP());
2602     if (!process_sp) {
2603       SetError("Attached sucessfully but target has no process.");
2604       return;
2605     }
2606 
2607     if (attach_info.GetContinueOnceAttached())
2608       process_sp->Resume();
2609 
2610     window.GetParent()->RemoveSubWindow(&window);
2611   }
2612 
2613 protected:
2614   Debugger &m_debugger;
2615   WindowSP m_main_window_sp;
2616 
2617   ChoicesFieldDelegate *m_type_field;
2618   IntegerFieldDelegate *m_pid_field;
2619   TextFieldDelegate *m_name_field;
2620   BooleanFieldDelegate *m_continue_field;
2621   BooleanFieldDelegate *m_wait_for_field;
2622   BooleanFieldDelegate *m_include_existing_field;
2623   BooleanFieldDelegate *m_show_advanced_field;
2624   ProcessPluginFieldDelegate *m_plugin_field;
2625 };
2626 
2627 class MenuDelegate {
2628 public:
2629   virtual ~MenuDelegate() = default;
2630 
2631   virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0;
2632 };
2633 
2634 class Menu : public WindowDelegate {
2635 public:
2636   enum class Type { Invalid, Bar, Item, Separator };
2637 
2638   // Menubar or separator constructor
2639   Menu(Type type);
2640 
2641   // Menuitem constructor
2642   Menu(const char *name, const char *key_name, int key_value,
2643        uint64_t identifier);
2644 
2645   ~Menu() override = default;
2646 
2647   const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; }
2648 
2649   void SetDelegate(const MenuDelegateSP &delegate_sp) {
2650     m_delegate_sp = delegate_sp;
2651   }
2652 
2653   void RecalculateNameLengths();
2654 
2655   void AddSubmenu(const MenuSP &menu_sp);
2656 
2657   int DrawAndRunMenu(Window &window);
2658 
2659   void DrawMenuTitle(Window &window, bool highlight);
2660 
2661   bool WindowDelegateDraw(Window &window, bool force) override;
2662 
2663   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
2664 
2665   MenuActionResult ActionPrivate(Menu &menu) {
2666     MenuActionResult result = MenuActionResult::NotHandled;
2667     if (m_delegate_sp) {
2668       result = m_delegate_sp->MenuDelegateAction(menu);
2669       if (result != MenuActionResult::NotHandled)
2670         return result;
2671     } else if (m_parent) {
2672       result = m_parent->ActionPrivate(menu);
2673       if (result != MenuActionResult::NotHandled)
2674         return result;
2675     }
2676     return m_canned_result;
2677   }
2678 
2679   MenuActionResult Action() {
2680     // Call the recursive action so it can try to handle it with the menu
2681     // delegate, and if not, try our parent menu
2682     return ActionPrivate(*this);
2683   }
2684 
2685   void SetCannedResult(MenuActionResult result) { m_canned_result = result; }
2686 
2687   Menus &GetSubmenus() { return m_submenus; }
2688 
2689   const Menus &GetSubmenus() const { return m_submenus; }
2690 
2691   int GetSelectedSubmenuIndex() const { return m_selected; }
2692 
2693   void SetSelectedSubmenuIndex(int idx) { m_selected = idx; }
2694 
2695   Type GetType() const { return m_type; }
2696 
2697   int GetStartingColumn() const { return m_start_col; }
2698 
2699   void SetStartingColumn(int col) { m_start_col = col; }
2700 
2701   int GetKeyValue() const { return m_key_value; }
2702 
2703   std::string &GetName() { return m_name; }
2704 
2705   int GetDrawWidth() const {
2706     return m_max_submenu_name_length + m_max_submenu_key_name_length + 8;
2707   }
2708 
2709   uint64_t GetIdentifier() const { return m_identifier; }
2710 
2711   void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
2712 
2713 protected:
2714   std::string m_name;
2715   std::string m_key_name;
2716   uint64_t m_identifier;
2717   Type m_type;
2718   int m_key_value;
2719   int m_start_col;
2720   int m_max_submenu_name_length;
2721   int m_max_submenu_key_name_length;
2722   int m_selected;
2723   Menu *m_parent;
2724   Menus m_submenus;
2725   WindowSP m_menu_window_sp;
2726   MenuActionResult m_canned_result;
2727   MenuDelegateSP m_delegate_sp;
2728 };
2729 
2730 // Menubar or separator constructor
2731 Menu::Menu(Type type)
2732     : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0),
2733       m_start_col(0), m_max_submenu_name_length(0),
2734       m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
2735       m_submenus(), m_canned_result(MenuActionResult::NotHandled),
2736       m_delegate_sp() {}
2737 
2738 // Menuitem constructor
2739 Menu::Menu(const char *name, const char *key_name, int key_value,
2740            uint64_t identifier)
2741     : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid),
2742       m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0),
2743       m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
2744       m_submenus(), m_canned_result(MenuActionResult::NotHandled),
2745       m_delegate_sp() {
2746   if (name && name[0]) {
2747     m_name = name;
2748     m_type = Type::Item;
2749     if (key_name && key_name[0])
2750       m_key_name = key_name;
2751   } else {
2752     m_type = Type::Separator;
2753   }
2754 }
2755 
2756 void Menu::RecalculateNameLengths() {
2757   m_max_submenu_name_length = 0;
2758   m_max_submenu_key_name_length = 0;
2759   Menus &submenus = GetSubmenus();
2760   const size_t num_submenus = submenus.size();
2761   for (size_t i = 0; i < num_submenus; ++i) {
2762     Menu *submenu = submenus[i].get();
2763     if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size())
2764       m_max_submenu_name_length = submenu->m_name.size();
2765     if (static_cast<size_t>(m_max_submenu_key_name_length) <
2766         submenu->m_key_name.size())
2767       m_max_submenu_key_name_length = submenu->m_key_name.size();
2768   }
2769 }
2770 
2771 void Menu::AddSubmenu(const MenuSP &menu_sp) {
2772   menu_sp->m_parent = this;
2773   if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size())
2774     m_max_submenu_name_length = menu_sp->m_name.size();
2775   if (static_cast<size_t>(m_max_submenu_key_name_length) <
2776       menu_sp->m_key_name.size())
2777     m_max_submenu_key_name_length = menu_sp->m_key_name.size();
2778   m_submenus.push_back(menu_sp);
2779 }
2780 
2781 void Menu::DrawMenuTitle(Window &window, bool highlight) {
2782   if (m_type == Type::Separator) {
2783     window.MoveCursor(0, window.GetCursorY());
2784     window.PutChar(ACS_LTEE);
2785     int width = window.GetWidth();
2786     if (width > 2) {
2787       width -= 2;
2788       for (int i = 0; i < width; ++i)
2789         window.PutChar(ACS_HLINE);
2790     }
2791     window.PutChar(ACS_RTEE);
2792   } else {
2793     const int shortcut_key = m_key_value;
2794     bool underlined_shortcut = false;
2795     const attr_t highlight_attr = A_REVERSE;
2796     if (highlight)
2797       window.AttributeOn(highlight_attr);
2798     if (llvm::isPrint(shortcut_key)) {
2799       size_t lower_pos = m_name.find(tolower(shortcut_key));
2800       size_t upper_pos = m_name.find(toupper(shortcut_key));
2801       const char *name = m_name.c_str();
2802       size_t pos = std::min<size_t>(lower_pos, upper_pos);
2803       if (pos != std::string::npos) {
2804         underlined_shortcut = true;
2805         if (pos > 0) {
2806           window.PutCString(name, pos);
2807           name += pos;
2808         }
2809         const attr_t shortcut_attr = A_UNDERLINE | A_BOLD;
2810         window.AttributeOn(shortcut_attr);
2811         window.PutChar(name[0]);
2812         window.AttributeOff(shortcut_attr);
2813         name++;
2814         if (name[0])
2815           window.PutCString(name);
2816       }
2817     }
2818 
2819     if (!underlined_shortcut) {
2820       window.PutCString(m_name.c_str());
2821     }
2822 
2823     if (highlight)
2824       window.AttributeOff(highlight_attr);
2825 
2826     if (m_key_name.empty()) {
2827       if (!underlined_shortcut && llvm::isPrint(m_key_value)) {
2828         window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
2829         window.Printf(" (%c)", m_key_value);
2830         window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
2831       }
2832     } else {
2833       window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
2834       window.Printf(" (%s)", m_key_name.c_str());
2835       window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
2836     }
2837   }
2838 }
2839 
2840 bool Menu::WindowDelegateDraw(Window &window, bool force) {
2841   Menus &submenus = GetSubmenus();
2842   const size_t num_submenus = submenus.size();
2843   const int selected_idx = GetSelectedSubmenuIndex();
2844   Menu::Type menu_type = GetType();
2845   switch (menu_type) {
2846   case Menu::Type::Bar: {
2847     window.SetBackground(BlackOnWhite);
2848     window.MoveCursor(0, 0);
2849     for (size_t i = 0; i < num_submenus; ++i) {
2850       Menu *menu = submenus[i].get();
2851       if (i > 0)
2852         window.PutChar(' ');
2853       menu->SetStartingColumn(window.GetCursorX());
2854       window.PutCString("| ");
2855       menu->DrawMenuTitle(window, false);
2856     }
2857     window.PutCString(" |");
2858   } break;
2859 
2860   case Menu::Type::Item: {
2861     int y = 1;
2862     int x = 3;
2863     // Draw the menu
2864     int cursor_x = 0;
2865     int cursor_y = 0;
2866     window.Erase();
2867     window.SetBackground(BlackOnWhite);
2868     window.Box();
2869     for (size_t i = 0; i < num_submenus; ++i) {
2870       const bool is_selected = (i == static_cast<size_t>(selected_idx));
2871       window.MoveCursor(x, y + i);
2872       if (is_selected) {
2873         // Remember where we want the cursor to be
2874         cursor_x = x - 1;
2875         cursor_y = y + i;
2876       }
2877       submenus[i]->DrawMenuTitle(window, is_selected);
2878     }
2879     window.MoveCursor(cursor_x, cursor_y);
2880   } break;
2881 
2882   default:
2883   case Menu::Type::Separator:
2884     break;
2885   }
2886   return true; // Drawing handled...
2887 }
2888 
2889 HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) {
2890   HandleCharResult result = eKeyNotHandled;
2891 
2892   Menus &submenus = GetSubmenus();
2893   const size_t num_submenus = submenus.size();
2894   const int selected_idx = GetSelectedSubmenuIndex();
2895   Menu::Type menu_type = GetType();
2896   if (menu_type == Menu::Type::Bar) {
2897     MenuSP run_menu_sp;
2898     switch (key) {
2899     case KEY_DOWN:
2900     case KEY_UP:
2901       // Show last menu or first menu
2902       if (selected_idx < static_cast<int>(num_submenus))
2903         run_menu_sp = submenus[selected_idx];
2904       else if (!submenus.empty())
2905         run_menu_sp = submenus.front();
2906       result = eKeyHandled;
2907       break;
2908 
2909     case KEY_RIGHT:
2910       ++m_selected;
2911       if (m_selected >= static_cast<int>(num_submenus))
2912         m_selected = 0;
2913       if (m_selected < static_cast<int>(num_submenus))
2914         run_menu_sp = submenus[m_selected];
2915       else if (!submenus.empty())
2916         run_menu_sp = submenus.front();
2917       result = eKeyHandled;
2918       break;
2919 
2920     case KEY_LEFT:
2921       --m_selected;
2922       if (m_selected < 0)
2923         m_selected = num_submenus - 1;
2924       if (m_selected < static_cast<int>(num_submenus))
2925         run_menu_sp = submenus[m_selected];
2926       else if (!submenus.empty())
2927         run_menu_sp = submenus.front();
2928       result = eKeyHandled;
2929       break;
2930 
2931     default:
2932       for (size_t i = 0; i < num_submenus; ++i) {
2933         if (submenus[i]->GetKeyValue() == key) {
2934           SetSelectedSubmenuIndex(i);
2935           run_menu_sp = submenus[i];
2936           result = eKeyHandled;
2937           break;
2938         }
2939       }
2940       break;
2941     }
2942 
2943     if (run_menu_sp) {
2944       // Run the action on this menu in case we need to populate the menu with
2945       // dynamic content and also in case check marks, and any other menu
2946       // decorations need to be calculated
2947       if (run_menu_sp->Action() == MenuActionResult::Quit)
2948         return eQuitApplication;
2949 
2950       Rect menu_bounds;
2951       menu_bounds.origin.x = run_menu_sp->GetStartingColumn();
2952       menu_bounds.origin.y = 1;
2953       menu_bounds.size.width = run_menu_sp->GetDrawWidth();
2954       menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2;
2955       if (m_menu_window_sp)
2956         window.GetParent()->RemoveSubWindow(m_menu_window_sp.get());
2957 
2958       m_menu_window_sp = window.GetParent()->CreateSubWindow(
2959           run_menu_sp->GetName().c_str(), menu_bounds, true);
2960       m_menu_window_sp->SetDelegate(run_menu_sp);
2961     }
2962   } else if (menu_type == Menu::Type::Item) {
2963     switch (key) {
2964     case KEY_DOWN:
2965       if (m_submenus.size() > 1) {
2966         const int start_select = m_selected;
2967         while (++m_selected != start_select) {
2968           if (static_cast<size_t>(m_selected) >= num_submenus)
2969             m_selected = 0;
2970           if (m_submenus[m_selected]->GetType() == Type::Separator)
2971             continue;
2972           else
2973             break;
2974         }
2975         return eKeyHandled;
2976       }
2977       break;
2978 
2979     case KEY_UP:
2980       if (m_submenus.size() > 1) {
2981         const int start_select = m_selected;
2982         while (--m_selected != start_select) {
2983           if (m_selected < static_cast<int>(0))
2984             m_selected = num_submenus - 1;
2985           if (m_submenus[m_selected]->GetType() == Type::Separator)
2986             continue;
2987           else
2988             break;
2989         }
2990         return eKeyHandled;
2991       }
2992       break;
2993 
2994     case KEY_RETURN:
2995       if (static_cast<size_t>(selected_idx) < num_submenus) {
2996         if (submenus[selected_idx]->Action() == MenuActionResult::Quit)
2997           return eQuitApplication;
2998         window.GetParent()->RemoveSubWindow(&window);
2999         return eKeyHandled;
3000       }
3001       break;
3002 
3003     case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in
3004                      // case other chars are entered for escaped sequences
3005       window.GetParent()->RemoveSubWindow(&window);
3006       return eKeyHandled;
3007 
3008     default:
3009       for (size_t i = 0; i < num_submenus; ++i) {
3010         Menu *menu = submenus[i].get();
3011         if (menu->GetKeyValue() == key) {
3012           SetSelectedSubmenuIndex(i);
3013           window.GetParent()->RemoveSubWindow(&window);
3014           if (menu->Action() == MenuActionResult::Quit)
3015             return eQuitApplication;
3016           return eKeyHandled;
3017         }
3018       }
3019       break;
3020     }
3021   } else if (menu_type == Menu::Type::Separator) {
3022   }
3023   return result;
3024 }
3025 
3026 class Application {
3027 public:
3028   Application(FILE *in, FILE *out)
3029       : m_window_sp(), m_screen(nullptr), m_in(in), m_out(out) {}
3030 
3031   ~Application() {
3032     m_window_delegates.clear();
3033     m_window_sp.reset();
3034     if (m_screen) {
3035       ::delscreen(m_screen);
3036       m_screen = nullptr;
3037     }
3038   }
3039 
3040   void Initialize() {
3041     m_screen = ::newterm(nullptr, m_out, m_in);
3042     ::start_color();
3043     ::curs_set(0);
3044     ::noecho();
3045     ::keypad(stdscr, TRUE);
3046   }
3047 
3048   void Terminate() { ::endwin(); }
3049 
3050   void Run(Debugger &debugger) {
3051     bool done = false;
3052     int delay_in_tenths_of_a_second = 1;
3053 
3054     // Alas the threading model in curses is a bit lame so we need to resort to
3055     // polling every 0.5 seconds. We could poll for stdin ourselves and then
3056     // pass the keys down but then we need to translate all of the escape
3057     // sequences ourselves. So we resort to polling for input because we need
3058     // to receive async process events while in this loop.
3059 
3060     halfdelay(delay_in_tenths_of_a_second); // Poll using some number of tenths
3061                                             // of seconds seconds when calling
3062                                             // Window::GetChar()
3063 
3064     ListenerSP listener_sp(
3065         Listener::MakeListener("lldb.IOHandler.curses.Application"));
3066     ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass());
3067     debugger.EnableForwardEvents(listener_sp);
3068 
3069     m_update_screen = true;
3070 #if defined(__APPLE__)
3071     std::deque<int> escape_chars;
3072 #endif
3073 
3074     while (!done) {
3075       if (m_update_screen) {
3076         m_window_sp->Draw(false);
3077         // All windows should be calling Window::DeferredRefresh() instead of
3078         // Window::Refresh() so we can do a single update and avoid any screen
3079         // blinking
3080         update_panels();
3081 
3082         // Cursor hiding isn't working on MacOSX, so hide it in the top left
3083         // corner
3084         m_window_sp->MoveCursor(0, 0);
3085 
3086         doupdate();
3087         m_update_screen = false;
3088       }
3089 
3090 #if defined(__APPLE__)
3091       // Terminal.app doesn't map its function keys correctly, F1-F4 default
3092       // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if
3093       // possible
3094       int ch;
3095       if (escape_chars.empty())
3096         ch = m_window_sp->GetChar();
3097       else {
3098         ch = escape_chars.front();
3099         escape_chars.pop_front();
3100       }
3101       if (ch == KEY_ESCAPE) {
3102         int ch2 = m_window_sp->GetChar();
3103         if (ch2 == 'O') {
3104           int ch3 = m_window_sp->GetChar();
3105           switch (ch3) {
3106           case 'P':
3107             ch = KEY_F(1);
3108             break;
3109           case 'Q':
3110             ch = KEY_F(2);
3111             break;
3112           case 'R':
3113             ch = KEY_F(3);
3114             break;
3115           case 'S':
3116             ch = KEY_F(4);
3117             break;
3118           default:
3119             escape_chars.push_back(ch2);
3120             if (ch3 != -1)
3121               escape_chars.push_back(ch3);
3122             break;
3123           }
3124         } else if (ch2 != -1)
3125           escape_chars.push_back(ch2);
3126       }
3127 #else
3128       int ch = m_window_sp->GetChar();
3129 
3130 #endif
3131       if (ch == -1) {
3132         if (feof(m_in) || ferror(m_in)) {
3133           done = true;
3134         } else {
3135           // Just a timeout from using halfdelay(), check for events
3136           EventSP event_sp;
3137           while (listener_sp->PeekAtNextEvent()) {
3138             listener_sp->GetEvent(event_sp, std::chrono::seconds(0));
3139 
3140             if (event_sp) {
3141               Broadcaster *broadcaster = event_sp->GetBroadcaster();
3142               if (broadcaster) {
3143                 // uint32_t event_type = event_sp->GetType();
3144                 ConstString broadcaster_class(
3145                     broadcaster->GetBroadcasterClass());
3146                 if (broadcaster_class == broadcaster_class_process) {
3147                   m_update_screen = true;
3148                   continue; // Don't get any key, just update our view
3149                 }
3150               }
3151             }
3152           }
3153         }
3154       } else {
3155         HandleCharResult key_result = m_window_sp->HandleChar(ch);
3156         switch (key_result) {
3157         case eKeyHandled:
3158           m_update_screen = true;
3159           break;
3160         case eKeyNotHandled:
3161           if (ch == 12) { // Ctrl+L, force full redraw
3162             redrawwin(m_window_sp->get());
3163             m_update_screen = true;
3164           }
3165           break;
3166         case eQuitApplication:
3167           done = true;
3168           break;
3169         }
3170       }
3171     }
3172 
3173     debugger.CancelForwardEvents(listener_sp);
3174   }
3175 
3176   WindowSP &GetMainWindow() {
3177     if (!m_window_sp)
3178       m_window_sp = std::make_shared<Window>("main", stdscr, false);
3179     return m_window_sp;
3180   }
3181 
3182   void TerminalSizeChanged() {
3183     ::endwin();
3184     ::refresh();
3185     Rect content_bounds = m_window_sp->GetFrame();
3186     m_window_sp->SetBounds(content_bounds);
3187     if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar"))
3188       menubar_window_sp->SetBounds(content_bounds.MakeMenuBar());
3189     if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status"))
3190       status_window_sp->SetBounds(content_bounds.MakeStatusBar());
3191 
3192     WindowSP source_window_sp = m_window_sp->FindSubWindow("Source");
3193     WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables");
3194     WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers");
3195     WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads");
3196 
3197     Rect threads_bounds;
3198     Rect source_variables_bounds;
3199     content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
3200                                            threads_bounds);
3201     if (threads_window_sp)
3202       threads_window_sp->SetBounds(threads_bounds);
3203     else
3204       source_variables_bounds = content_bounds;
3205 
3206     Rect source_bounds;
3207     Rect variables_registers_bounds;
3208     source_variables_bounds.HorizontalSplitPercentage(
3209         0.70, source_bounds, variables_registers_bounds);
3210     if (variables_window_sp || registers_window_sp) {
3211       if (variables_window_sp && registers_window_sp) {
3212         Rect variables_bounds;
3213         Rect registers_bounds;
3214         variables_registers_bounds.VerticalSplitPercentage(
3215             0.50, variables_bounds, registers_bounds);
3216         variables_window_sp->SetBounds(variables_bounds);
3217         registers_window_sp->SetBounds(registers_bounds);
3218       } else if (variables_window_sp) {
3219         variables_window_sp->SetBounds(variables_registers_bounds);
3220       } else {
3221         registers_window_sp->SetBounds(variables_registers_bounds);
3222       }
3223     } else {
3224       source_bounds = source_variables_bounds;
3225     }
3226 
3227     source_window_sp->SetBounds(source_bounds);
3228 
3229     touchwin(stdscr);
3230     redrawwin(m_window_sp->get());
3231     m_update_screen = true;
3232   }
3233 
3234 protected:
3235   WindowSP m_window_sp;
3236   WindowDelegates m_window_delegates;
3237   SCREEN *m_screen;
3238   FILE *m_in;
3239   FILE *m_out;
3240   bool m_update_screen = false;
3241 };
3242 
3243 } // namespace curses
3244 
3245 using namespace curses;
3246 
3247 struct Row {
3248   ValueObjectUpdater value;
3249   Row *parent;
3250   // The process stop ID when the children were calculated.
3251   uint32_t children_stop_id = 0;
3252   int row_idx = 0;
3253   int x = 1;
3254   int y = 1;
3255   bool might_have_children;
3256   bool expanded = false;
3257   bool calculated_children = false;
3258   std::vector<Row> children;
3259 
3260   Row(const ValueObjectSP &v, Row *p)
3261       : value(v), parent(p),
3262         might_have_children(v ? v->MightHaveChildren() : false) {}
3263 
3264   size_t GetDepth() const {
3265     if (parent)
3266       return 1 + parent->GetDepth();
3267     return 0;
3268   }
3269 
3270   void Expand() { expanded = true; }
3271 
3272   std::vector<Row> &GetChildren() {
3273     ProcessSP process_sp = value.GetProcessSP();
3274     auto stop_id = process_sp->GetStopID();
3275     if (process_sp && stop_id != children_stop_id) {
3276       children_stop_id = stop_id;
3277       calculated_children = false;
3278     }
3279     if (!calculated_children) {
3280       children.clear();
3281       calculated_children = true;
3282       ValueObjectSP valobj = value.GetSP();
3283       if (valobj) {
3284         const size_t num_children = valobj->GetNumChildren();
3285         for (size_t i = 0; i < num_children; ++i) {
3286           children.push_back(Row(valobj->GetChildAtIndex(i, true), this));
3287         }
3288       }
3289     }
3290     return children;
3291   }
3292 
3293   void Unexpand() {
3294     expanded = false;
3295     calculated_children = false;
3296     children.clear();
3297   }
3298 
3299   void DrawTree(Window &window) {
3300     if (parent)
3301       parent->DrawTreeForChild(window, this, 0);
3302 
3303     if (might_have_children) {
3304       // It we can get UTF8 characters to work we should try to use the
3305       // "symbol" UTF8 string below
3306       //            const char *symbol = "";
3307       //            if (row.expanded)
3308       //                symbol = "\xe2\x96\xbd ";
3309       //            else
3310       //                symbol = "\xe2\x96\xb7 ";
3311       //            window.PutCString (symbol);
3312 
3313       // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v'
3314       // or '>' character...
3315       //            if (expanded)
3316       //                window.PutChar (ACS_DARROW);
3317       //            else
3318       //                window.PutChar (ACS_RARROW);
3319       // Since we can't find any good looking right arrow/down arrow symbols,
3320       // just use a diamond...
3321       window.PutChar(ACS_DIAMOND);
3322       window.PutChar(ACS_HLINE);
3323     }
3324   }
3325 
3326   void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) {
3327     if (parent)
3328       parent->DrawTreeForChild(window, this, reverse_depth + 1);
3329 
3330     if (&GetChildren().back() == child) {
3331       // Last child
3332       if (reverse_depth == 0) {
3333         window.PutChar(ACS_LLCORNER);
3334         window.PutChar(ACS_HLINE);
3335       } else {
3336         window.PutChar(' ');
3337         window.PutChar(' ');
3338       }
3339     } else {
3340       if (reverse_depth == 0) {
3341         window.PutChar(ACS_LTEE);
3342         window.PutChar(ACS_HLINE);
3343       } else {
3344         window.PutChar(ACS_VLINE);
3345         window.PutChar(' ');
3346       }
3347     }
3348   }
3349 };
3350 
3351 struct DisplayOptions {
3352   bool show_types;
3353 };
3354 
3355 class TreeItem;
3356 
3357 class TreeDelegate {
3358 public:
3359   TreeDelegate() = default;
3360   virtual ~TreeDelegate() = default;
3361 
3362   virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0;
3363   virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0;
3364   virtual bool TreeDelegateItemSelected(
3365       TreeItem &item) = 0; // Return true if we need to update views
3366 };
3367 
3368 typedef std::shared_ptr<TreeDelegate> TreeDelegateSP;
3369 
3370 class TreeItem {
3371 public:
3372   TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children)
3373       : m_parent(parent), m_delegate(delegate), m_user_data(nullptr),
3374         m_identifier(0), m_row_idx(-1), m_children(),
3375         m_might_have_children(might_have_children), m_is_expanded(false) {}
3376 
3377   TreeItem &operator=(const TreeItem &rhs) {
3378     if (this != &rhs) {
3379       m_parent = rhs.m_parent;
3380       m_delegate = rhs.m_delegate;
3381       m_user_data = rhs.m_user_data;
3382       m_identifier = rhs.m_identifier;
3383       m_row_idx = rhs.m_row_idx;
3384       m_children = rhs.m_children;
3385       m_might_have_children = rhs.m_might_have_children;
3386       m_is_expanded = rhs.m_is_expanded;
3387     }
3388     return *this;
3389   }
3390 
3391   TreeItem(const TreeItem &) = default;
3392 
3393   size_t GetDepth() const {
3394     if (m_parent)
3395       return 1 + m_parent->GetDepth();
3396     return 0;
3397   }
3398 
3399   int GetRowIndex() const { return m_row_idx; }
3400 
3401   void ClearChildren() { m_children.clear(); }
3402 
3403   void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); }
3404 
3405   TreeItem &operator[](size_t i) { return m_children[i]; }
3406 
3407   void SetRowIndex(int row_idx) { m_row_idx = row_idx; }
3408 
3409   size_t GetNumChildren() {
3410     m_delegate.TreeDelegateGenerateChildren(*this);
3411     return m_children.size();
3412   }
3413 
3414   void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); }
3415 
3416   void CalculateRowIndexes(int &row_idx) {
3417     SetRowIndex(row_idx);
3418     ++row_idx;
3419 
3420     const bool expanded = IsExpanded();
3421 
3422     // The root item must calculate its children, or we must calculate the
3423     // number of children if the item is expanded
3424     if (m_parent == nullptr || expanded)
3425       GetNumChildren();
3426 
3427     for (auto &item : m_children) {
3428       if (expanded)
3429         item.CalculateRowIndexes(row_idx);
3430       else
3431         item.SetRowIndex(-1);
3432     }
3433   }
3434 
3435   TreeItem *GetParent() { return m_parent; }
3436 
3437   bool IsExpanded() const { return m_is_expanded; }
3438 
3439   void Expand() { m_is_expanded = true; }
3440 
3441   void Unexpand() { m_is_expanded = false; }
3442 
3443   bool Draw(Window &window, const int first_visible_row,
3444             const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) {
3445     if (num_rows_left <= 0)
3446       return false;
3447 
3448     if (m_row_idx >= first_visible_row) {
3449       window.MoveCursor(2, row_idx + 1);
3450 
3451       if (m_parent)
3452         m_parent->DrawTreeForChild(window, this, 0);
3453 
3454       if (m_might_have_children) {
3455         // It we can get UTF8 characters to work we should try to use the
3456         // "symbol" UTF8 string below
3457         //            const char *symbol = "";
3458         //            if (row.expanded)
3459         //                symbol = "\xe2\x96\xbd ";
3460         //            else
3461         //                symbol = "\xe2\x96\xb7 ";
3462         //            window.PutCString (symbol);
3463 
3464         // The ACS_DARROW and ACS_RARROW don't look very nice they are just a
3465         // 'v' or '>' character...
3466         //            if (expanded)
3467         //                window.PutChar (ACS_DARROW);
3468         //            else
3469         //                window.PutChar (ACS_RARROW);
3470         // Since we can't find any good looking right arrow/down arrow symbols,
3471         // just use a diamond...
3472         window.PutChar(ACS_DIAMOND);
3473         window.PutChar(ACS_HLINE);
3474       }
3475       bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) &&
3476                        window.IsActive();
3477 
3478       if (highlight)
3479         window.AttributeOn(A_REVERSE);
3480 
3481       m_delegate.TreeDelegateDrawTreeItem(*this, window);
3482 
3483       if (highlight)
3484         window.AttributeOff(A_REVERSE);
3485       ++row_idx;
3486       --num_rows_left;
3487     }
3488 
3489     if (num_rows_left <= 0)
3490       return false; // We are done drawing...
3491 
3492     if (IsExpanded()) {
3493       for (auto &item : m_children) {
3494         // If we displayed all the rows and item.Draw() returns false we are
3495         // done drawing and can exit this for loop
3496         if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx,
3497                        num_rows_left))
3498           break;
3499       }
3500     }
3501     return num_rows_left >= 0; // Return true if not done drawing yet
3502   }
3503 
3504   void DrawTreeForChild(Window &window, TreeItem *child,
3505                         uint32_t reverse_depth) {
3506     if (m_parent)
3507       m_parent->DrawTreeForChild(window, this, reverse_depth + 1);
3508 
3509     if (&m_children.back() == child) {
3510       // Last child
3511       if (reverse_depth == 0) {
3512         window.PutChar(ACS_LLCORNER);
3513         window.PutChar(ACS_HLINE);
3514       } else {
3515         window.PutChar(' ');
3516         window.PutChar(' ');
3517       }
3518     } else {
3519       if (reverse_depth == 0) {
3520         window.PutChar(ACS_LTEE);
3521         window.PutChar(ACS_HLINE);
3522       } else {
3523         window.PutChar(ACS_VLINE);
3524         window.PutChar(' ');
3525       }
3526     }
3527   }
3528 
3529   TreeItem *GetItemForRowIndex(uint32_t row_idx) {
3530     if (static_cast<uint32_t>(m_row_idx) == row_idx)
3531       return this;
3532     if (m_children.empty())
3533       return nullptr;
3534     if (IsExpanded()) {
3535       for (auto &item : m_children) {
3536         TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx);
3537         if (selected_item_ptr)
3538           return selected_item_ptr;
3539       }
3540     }
3541     return nullptr;
3542   }
3543 
3544   void *GetUserData() const { return m_user_data; }
3545 
3546   void SetUserData(void *user_data) { m_user_data = user_data; }
3547 
3548   uint64_t GetIdentifier() const { return m_identifier; }
3549 
3550   void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
3551 
3552   void SetMightHaveChildren(bool b) { m_might_have_children = b; }
3553 
3554 protected:
3555   TreeItem *m_parent;
3556   TreeDelegate &m_delegate;
3557   void *m_user_data;
3558   uint64_t m_identifier;
3559   int m_row_idx; // Zero based visible row index, -1 if not visible or for the
3560                  // root item
3561   std::vector<TreeItem> m_children;
3562   bool m_might_have_children;
3563   bool m_is_expanded;
3564 };
3565 
3566 class TreeWindowDelegate : public WindowDelegate {
3567 public:
3568   TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp)
3569       : m_debugger(debugger), m_delegate_sp(delegate_sp),
3570         m_root(nullptr, *delegate_sp, true), m_selected_item(nullptr),
3571         m_num_rows(0), m_selected_row_idx(0), m_first_visible_row(0),
3572         m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {}
3573 
3574   int NumVisibleRows() const { return m_max_y - m_min_y; }
3575 
3576   bool WindowDelegateDraw(Window &window, bool force) override {
3577     ExecutionContext exe_ctx(
3578         m_debugger.GetCommandInterpreter().GetExecutionContext());
3579     Process *process = exe_ctx.GetProcessPtr();
3580 
3581     bool display_content = false;
3582     if (process) {
3583       StateType state = process->GetState();
3584       if (StateIsStoppedState(state, true)) {
3585         // We are stopped, so it is ok to
3586         display_content = true;
3587       } else if (StateIsRunningState(state)) {
3588         return true; // Don't do any updating when we are running
3589       }
3590     }
3591 
3592     m_min_x = 2;
3593     m_min_y = 1;
3594     m_max_x = window.GetWidth() - 1;
3595     m_max_y = window.GetHeight() - 1;
3596 
3597     window.Erase();
3598     window.DrawTitleBox(window.GetName());
3599 
3600     if (display_content) {
3601       const int num_visible_rows = NumVisibleRows();
3602       m_num_rows = 0;
3603       m_root.CalculateRowIndexes(m_num_rows);
3604 
3605       // If we unexpanded while having something selected our total number of
3606       // rows is less than the num visible rows, then make sure we show all the
3607       // rows by setting the first visible row accordingly.
3608       if (m_first_visible_row > 0 && m_num_rows < num_visible_rows)
3609         m_first_visible_row = 0;
3610 
3611       // Make sure the selected row is always visible
3612       if (m_selected_row_idx < m_first_visible_row)
3613         m_first_visible_row = m_selected_row_idx;
3614       else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
3615         m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
3616 
3617       int row_idx = 0;
3618       int num_rows_left = num_visible_rows;
3619       m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx,
3620                   num_rows_left);
3621       // Get the selected row
3622       m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3623     } else {
3624       m_selected_item = nullptr;
3625     }
3626 
3627     return true; // Drawing handled
3628   }
3629 
3630   const char *WindowDelegateGetHelpText() override {
3631     return "Thread window keyboard shortcuts:";
3632   }
3633 
3634   KeyHelp *WindowDelegateGetKeyHelp() override {
3635     static curses::KeyHelp g_source_view_key_help[] = {
3636         {KEY_UP, "Select previous item"},
3637         {KEY_DOWN, "Select next item"},
3638         {KEY_RIGHT, "Expand the selected item"},
3639         {KEY_LEFT,
3640          "Unexpand the selected item or select parent if not expanded"},
3641         {KEY_PPAGE, "Page up"},
3642         {KEY_NPAGE, "Page down"},
3643         {'h', "Show help dialog"},
3644         {' ', "Toggle item expansion"},
3645         {',', "Page up"},
3646         {'.', "Page down"},
3647         {'\0', nullptr}};
3648     return g_source_view_key_help;
3649   }
3650 
3651   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
3652     switch (c) {
3653     case ',':
3654     case KEY_PPAGE:
3655       // Page up key
3656       if (m_first_visible_row > 0) {
3657         if (m_first_visible_row > m_max_y)
3658           m_first_visible_row -= m_max_y;
3659         else
3660           m_first_visible_row = 0;
3661         m_selected_row_idx = m_first_visible_row;
3662         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3663         if (m_selected_item)
3664           m_selected_item->ItemWasSelected();
3665       }
3666       return eKeyHandled;
3667 
3668     case '.':
3669     case KEY_NPAGE:
3670       // Page down key
3671       if (m_num_rows > m_max_y) {
3672         if (m_first_visible_row + m_max_y < m_num_rows) {
3673           m_first_visible_row += m_max_y;
3674           m_selected_row_idx = m_first_visible_row;
3675           m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3676           if (m_selected_item)
3677             m_selected_item->ItemWasSelected();
3678         }
3679       }
3680       return eKeyHandled;
3681 
3682     case KEY_UP:
3683       if (m_selected_row_idx > 0) {
3684         --m_selected_row_idx;
3685         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3686         if (m_selected_item)
3687           m_selected_item->ItemWasSelected();
3688       }
3689       return eKeyHandled;
3690 
3691     case KEY_DOWN:
3692       if (m_selected_row_idx + 1 < m_num_rows) {
3693         ++m_selected_row_idx;
3694         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3695         if (m_selected_item)
3696           m_selected_item->ItemWasSelected();
3697       }
3698       return eKeyHandled;
3699 
3700     case KEY_RIGHT:
3701       if (m_selected_item) {
3702         if (!m_selected_item->IsExpanded())
3703           m_selected_item->Expand();
3704       }
3705       return eKeyHandled;
3706 
3707     case KEY_LEFT:
3708       if (m_selected_item) {
3709         if (m_selected_item->IsExpanded())
3710           m_selected_item->Unexpand();
3711         else if (m_selected_item->GetParent()) {
3712           m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex();
3713           m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3714           if (m_selected_item)
3715             m_selected_item->ItemWasSelected();
3716         }
3717       }
3718       return eKeyHandled;
3719 
3720     case ' ':
3721       // Toggle expansion state when SPACE is pressed
3722       if (m_selected_item) {
3723         if (m_selected_item->IsExpanded())
3724           m_selected_item->Unexpand();
3725         else
3726           m_selected_item->Expand();
3727       }
3728       return eKeyHandled;
3729 
3730     case 'h':
3731       window.CreateHelpSubwindow();
3732       return eKeyHandled;
3733 
3734     default:
3735       break;
3736     }
3737     return eKeyNotHandled;
3738   }
3739 
3740 protected:
3741   Debugger &m_debugger;
3742   TreeDelegateSP m_delegate_sp;
3743   TreeItem m_root;
3744   TreeItem *m_selected_item;
3745   int m_num_rows;
3746   int m_selected_row_idx;
3747   int m_first_visible_row;
3748   int m_min_x;
3749   int m_min_y;
3750   int m_max_x;
3751   int m_max_y;
3752 };
3753 
3754 class FrameTreeDelegate : public TreeDelegate {
3755 public:
3756   FrameTreeDelegate() : TreeDelegate() {
3757     FormatEntity::Parse(
3758         "frame #${frame.index}: {${function.name}${function.pc-offset}}}",
3759         m_format);
3760   }
3761 
3762   ~FrameTreeDelegate() override = default;
3763 
3764   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
3765     Thread *thread = (Thread *)item.GetUserData();
3766     if (thread) {
3767       const uint64_t frame_idx = item.GetIdentifier();
3768       StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx);
3769       if (frame_sp) {
3770         StreamString strm;
3771         const SymbolContext &sc =
3772             frame_sp->GetSymbolContext(eSymbolContextEverything);
3773         ExecutionContext exe_ctx(frame_sp);
3774         if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr,
3775                                  nullptr, false, false)) {
3776           int right_pad = 1;
3777           window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
3778         }
3779       }
3780     }
3781   }
3782 
3783   void TreeDelegateGenerateChildren(TreeItem &item) override {
3784     // No children for frames yet...
3785   }
3786 
3787   bool TreeDelegateItemSelected(TreeItem &item) override {
3788     Thread *thread = (Thread *)item.GetUserData();
3789     if (thread) {
3790       thread->GetProcess()->GetThreadList().SetSelectedThreadByID(
3791           thread->GetID());
3792       const uint64_t frame_idx = item.GetIdentifier();
3793       thread->SetSelectedFrameByIndex(frame_idx);
3794       return true;
3795     }
3796     return false;
3797   }
3798 
3799 protected:
3800   FormatEntity::Entry m_format;
3801 };
3802 
3803 class ThreadTreeDelegate : public TreeDelegate {
3804 public:
3805   ThreadTreeDelegate(Debugger &debugger)
3806       : TreeDelegate(), m_debugger(debugger), m_tid(LLDB_INVALID_THREAD_ID),
3807         m_stop_id(UINT32_MAX) {
3808     FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop "
3809                         "reason = ${thread.stop-reason}}",
3810                         m_format);
3811   }
3812 
3813   ~ThreadTreeDelegate() override = default;
3814 
3815   ProcessSP GetProcess() {
3816     return m_debugger.GetCommandInterpreter()
3817         .GetExecutionContext()
3818         .GetProcessSP();
3819   }
3820 
3821   ThreadSP GetThread(const TreeItem &item) {
3822     ProcessSP process_sp = GetProcess();
3823     if (process_sp)
3824       return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier());
3825     return ThreadSP();
3826   }
3827 
3828   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
3829     ThreadSP thread_sp = GetThread(item);
3830     if (thread_sp) {
3831       StreamString strm;
3832       ExecutionContext exe_ctx(thread_sp);
3833       if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
3834                                nullptr, false, false)) {
3835         int right_pad = 1;
3836         window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
3837       }
3838     }
3839   }
3840 
3841   void TreeDelegateGenerateChildren(TreeItem &item) override {
3842     ProcessSP process_sp = GetProcess();
3843     if (process_sp && process_sp->IsAlive()) {
3844       StateType state = process_sp->GetState();
3845       if (StateIsStoppedState(state, true)) {
3846         ThreadSP thread_sp = GetThread(item);
3847         if (thread_sp) {
3848           if (m_stop_id == process_sp->GetStopID() &&
3849               thread_sp->GetID() == m_tid)
3850             return; // Children are already up to date
3851           if (!m_frame_delegate_sp) {
3852             // Always expand the thread item the first time we show it
3853             m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>();
3854           }
3855 
3856           m_stop_id = process_sp->GetStopID();
3857           m_tid = thread_sp->GetID();
3858 
3859           TreeItem t(&item, *m_frame_delegate_sp, false);
3860           size_t num_frames = thread_sp->GetStackFrameCount();
3861           item.Resize(num_frames, t);
3862           for (size_t i = 0; i < num_frames; ++i) {
3863             item[i].SetUserData(thread_sp.get());
3864             item[i].SetIdentifier(i);
3865           }
3866         }
3867         return;
3868       }
3869     }
3870     item.ClearChildren();
3871   }
3872 
3873   bool TreeDelegateItemSelected(TreeItem &item) override {
3874     ProcessSP process_sp = GetProcess();
3875     if (process_sp && process_sp->IsAlive()) {
3876       StateType state = process_sp->GetState();
3877       if (StateIsStoppedState(state, true)) {
3878         ThreadSP thread_sp = GetThread(item);
3879         if (thread_sp) {
3880           ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList();
3881           std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex());
3882           ThreadSP selected_thread_sp = thread_list.GetSelectedThread();
3883           if (selected_thread_sp->GetID() != thread_sp->GetID()) {
3884             thread_list.SetSelectedThreadByID(thread_sp->GetID());
3885             return true;
3886           }
3887         }
3888       }
3889     }
3890     return false;
3891   }
3892 
3893 protected:
3894   Debugger &m_debugger;
3895   std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp;
3896   lldb::user_id_t m_tid;
3897   uint32_t m_stop_id;
3898   FormatEntity::Entry m_format;
3899 };
3900 
3901 class ThreadsTreeDelegate : public TreeDelegate {
3902 public:
3903   ThreadsTreeDelegate(Debugger &debugger)
3904       : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger),
3905         m_stop_id(UINT32_MAX) {
3906     FormatEntity::Parse("process ${process.id}{, name = ${process.name}}",
3907                         m_format);
3908   }
3909 
3910   ~ThreadsTreeDelegate() override = default;
3911 
3912   ProcessSP GetProcess() {
3913     return m_debugger.GetCommandInterpreter()
3914         .GetExecutionContext()
3915         .GetProcessSP();
3916   }
3917 
3918   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
3919     ProcessSP process_sp = GetProcess();
3920     if (process_sp && process_sp->IsAlive()) {
3921       StreamString strm;
3922       ExecutionContext exe_ctx(process_sp);
3923       if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
3924                                nullptr, false, false)) {
3925         int right_pad = 1;
3926         window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
3927       }
3928     }
3929   }
3930 
3931   void TreeDelegateGenerateChildren(TreeItem &item) override {
3932     ProcessSP process_sp = GetProcess();
3933     if (process_sp && process_sp->IsAlive()) {
3934       StateType state = process_sp->GetState();
3935       if (StateIsStoppedState(state, true)) {
3936         const uint32_t stop_id = process_sp->GetStopID();
3937         if (m_stop_id == stop_id)
3938           return; // Children are already up to date
3939 
3940         m_stop_id = stop_id;
3941 
3942         if (!m_thread_delegate_sp) {
3943           // Always expand the thread item the first time we show it
3944           // item.Expand();
3945           m_thread_delegate_sp =
3946               std::make_shared<ThreadTreeDelegate>(m_debugger);
3947         }
3948 
3949         TreeItem t(&item, *m_thread_delegate_sp, false);
3950         ThreadList &threads = process_sp->GetThreadList();
3951         std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
3952         size_t num_threads = threads.GetSize();
3953         item.Resize(num_threads, t);
3954         for (size_t i = 0; i < num_threads; ++i) {
3955           item[i].SetIdentifier(threads.GetThreadAtIndex(i)->GetID());
3956           item[i].SetMightHaveChildren(true);
3957         }
3958         return;
3959       }
3960     }
3961     item.ClearChildren();
3962   }
3963 
3964   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
3965 
3966 protected:
3967   std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp;
3968   Debugger &m_debugger;
3969   uint32_t m_stop_id;
3970   FormatEntity::Entry m_format;
3971 };
3972 
3973 class ValueObjectListDelegate : public WindowDelegate {
3974 public:
3975   ValueObjectListDelegate() : m_rows() {}
3976 
3977   ValueObjectListDelegate(ValueObjectList &valobj_list)
3978       : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0),
3979         m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) {
3980     SetValues(valobj_list);
3981   }
3982 
3983   ~ValueObjectListDelegate() override = default;
3984 
3985   void SetValues(ValueObjectList &valobj_list) {
3986     m_selected_row = nullptr;
3987     m_selected_row_idx = 0;
3988     m_first_visible_row = 0;
3989     m_num_rows = 0;
3990     m_rows.clear();
3991     for (auto &valobj_sp : valobj_list.GetObjects())
3992       m_rows.push_back(Row(valobj_sp, nullptr));
3993   }
3994 
3995   bool WindowDelegateDraw(Window &window, bool force) override {
3996     m_num_rows = 0;
3997     m_min_x = 2;
3998     m_min_y = 1;
3999     m_max_x = window.GetWidth() - 1;
4000     m_max_y = window.GetHeight() - 1;
4001 
4002     window.Erase();
4003     window.DrawTitleBox(window.GetName());
4004 
4005     const int num_visible_rows = NumVisibleRows();
4006     const int num_rows = CalculateTotalNumberRows(m_rows);
4007 
4008     // If we unexpanded while having something selected our total number of
4009     // rows is less than the num visible rows, then make sure we show all the
4010     // rows by setting the first visible row accordingly.
4011     if (m_first_visible_row > 0 && num_rows < num_visible_rows)
4012       m_first_visible_row = 0;
4013 
4014     // Make sure the selected row is always visible
4015     if (m_selected_row_idx < m_first_visible_row)
4016       m_first_visible_row = m_selected_row_idx;
4017     else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
4018       m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
4019 
4020     DisplayRows(window, m_rows, g_options);
4021 
4022     // Get the selected row
4023     m_selected_row = GetRowForRowIndex(m_selected_row_idx);
4024     // Keep the cursor on the selected row so the highlight and the cursor are
4025     // always on the same line
4026     if (m_selected_row)
4027       window.MoveCursor(m_selected_row->x, m_selected_row->y);
4028 
4029     return true; // Drawing handled
4030   }
4031 
4032   KeyHelp *WindowDelegateGetKeyHelp() override {
4033     static curses::KeyHelp g_source_view_key_help[] = {
4034         {KEY_UP, "Select previous item"},
4035         {KEY_DOWN, "Select next item"},
4036         {KEY_RIGHT, "Expand selected item"},
4037         {KEY_LEFT, "Unexpand selected item or select parent if not expanded"},
4038         {KEY_PPAGE, "Page up"},
4039         {KEY_NPAGE, "Page down"},
4040         {'A', "Format as annotated address"},
4041         {'b', "Format as binary"},
4042         {'B', "Format as hex bytes with ASCII"},
4043         {'c', "Format as character"},
4044         {'d', "Format as a signed integer"},
4045         {'D', "Format selected value using the default format for the type"},
4046         {'f', "Format as float"},
4047         {'h', "Show help dialog"},
4048         {'i', "Format as instructions"},
4049         {'o', "Format as octal"},
4050         {'p', "Format as pointer"},
4051         {'s', "Format as C string"},
4052         {'t', "Toggle showing/hiding type names"},
4053         {'u', "Format as an unsigned integer"},
4054         {'x', "Format as hex"},
4055         {'X', "Format as uppercase hex"},
4056         {' ', "Toggle item expansion"},
4057         {',', "Page up"},
4058         {'.', "Page down"},
4059         {'\0', nullptr}};
4060     return g_source_view_key_help;
4061   }
4062 
4063   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
4064     switch (c) {
4065     case 'x':
4066     case 'X':
4067     case 'o':
4068     case 's':
4069     case 'u':
4070     case 'd':
4071     case 'D':
4072     case 'i':
4073     case 'A':
4074     case 'p':
4075     case 'c':
4076     case 'b':
4077     case 'B':
4078     case 'f':
4079       // Change the format for the currently selected item
4080       if (m_selected_row) {
4081         auto valobj_sp = m_selected_row->value.GetSP();
4082         if (valobj_sp)
4083           valobj_sp->SetFormat(FormatForChar(c));
4084       }
4085       return eKeyHandled;
4086 
4087     case 't':
4088       // Toggle showing type names
4089       g_options.show_types = !g_options.show_types;
4090       return eKeyHandled;
4091 
4092     case ',':
4093     case KEY_PPAGE:
4094       // Page up key
4095       if (m_first_visible_row > 0) {
4096         if (static_cast<int>(m_first_visible_row) > m_max_y)
4097           m_first_visible_row -= m_max_y;
4098         else
4099           m_first_visible_row = 0;
4100         m_selected_row_idx = m_first_visible_row;
4101       }
4102       return eKeyHandled;
4103 
4104     case '.':
4105     case KEY_NPAGE:
4106       // Page down key
4107       if (m_num_rows > static_cast<size_t>(m_max_y)) {
4108         if (m_first_visible_row + m_max_y < m_num_rows) {
4109           m_first_visible_row += m_max_y;
4110           m_selected_row_idx = m_first_visible_row;
4111         }
4112       }
4113       return eKeyHandled;
4114 
4115     case KEY_UP:
4116       if (m_selected_row_idx > 0)
4117         --m_selected_row_idx;
4118       return eKeyHandled;
4119 
4120     case KEY_DOWN:
4121       if (m_selected_row_idx + 1 < m_num_rows)
4122         ++m_selected_row_idx;
4123       return eKeyHandled;
4124 
4125     case KEY_RIGHT:
4126       if (m_selected_row) {
4127         if (!m_selected_row->expanded)
4128           m_selected_row->Expand();
4129       }
4130       return eKeyHandled;
4131 
4132     case KEY_LEFT:
4133       if (m_selected_row) {
4134         if (m_selected_row->expanded)
4135           m_selected_row->Unexpand();
4136         else if (m_selected_row->parent)
4137           m_selected_row_idx = m_selected_row->parent->row_idx;
4138       }
4139       return eKeyHandled;
4140 
4141     case ' ':
4142       // Toggle expansion state when SPACE is pressed
4143       if (m_selected_row) {
4144         if (m_selected_row->expanded)
4145           m_selected_row->Unexpand();
4146         else
4147           m_selected_row->Expand();
4148       }
4149       return eKeyHandled;
4150 
4151     case 'h':
4152       window.CreateHelpSubwindow();
4153       return eKeyHandled;
4154 
4155     default:
4156       break;
4157     }
4158     return eKeyNotHandled;
4159   }
4160 
4161 protected:
4162   std::vector<Row> m_rows;
4163   Row *m_selected_row = nullptr;
4164   uint32_t m_selected_row_idx = 0;
4165   uint32_t m_first_visible_row = 0;
4166   uint32_t m_num_rows = 0;
4167   int m_min_x;
4168   int m_min_y;
4169   int m_max_x = 0;
4170   int m_max_y = 0;
4171 
4172   static Format FormatForChar(int c) {
4173     switch (c) {
4174     case 'x':
4175       return eFormatHex;
4176     case 'X':
4177       return eFormatHexUppercase;
4178     case 'o':
4179       return eFormatOctal;
4180     case 's':
4181       return eFormatCString;
4182     case 'u':
4183       return eFormatUnsigned;
4184     case 'd':
4185       return eFormatDecimal;
4186     case 'D':
4187       return eFormatDefault;
4188     case 'i':
4189       return eFormatInstruction;
4190     case 'A':
4191       return eFormatAddressInfo;
4192     case 'p':
4193       return eFormatPointer;
4194     case 'c':
4195       return eFormatChar;
4196     case 'b':
4197       return eFormatBinary;
4198     case 'B':
4199       return eFormatBytesWithASCII;
4200     case 'f':
4201       return eFormatFloat;
4202     }
4203     return eFormatDefault;
4204   }
4205 
4206   bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options,
4207                         bool highlight, bool last_child) {
4208     ValueObject *valobj = row.value.GetSP().get();
4209 
4210     if (valobj == nullptr)
4211       return false;
4212 
4213     const char *type_name =
4214         options.show_types ? valobj->GetTypeName().GetCString() : nullptr;
4215     const char *name = valobj->GetName().GetCString();
4216     const char *value = valobj->GetValueAsCString();
4217     const char *summary = valobj->GetSummaryAsCString();
4218 
4219     window.MoveCursor(row.x, row.y);
4220 
4221     row.DrawTree(window);
4222 
4223     if (highlight)
4224       window.AttributeOn(A_REVERSE);
4225 
4226     if (type_name && type_name[0])
4227       window.PrintfTruncated(1, "(%s) ", type_name);
4228 
4229     if (name && name[0])
4230       window.PutCStringTruncated(1, name);
4231 
4232     attr_t changd_attr = 0;
4233     if (valobj->GetValueDidChange())
4234       changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD;
4235 
4236     if (value && value[0]) {
4237       window.PutCStringTruncated(1, " = ");
4238       if (changd_attr)
4239         window.AttributeOn(changd_attr);
4240       window.PutCStringTruncated(1, value);
4241       if (changd_attr)
4242         window.AttributeOff(changd_attr);
4243     }
4244 
4245     if (summary && summary[0]) {
4246       window.PutCStringTruncated(1, " ");
4247       if (changd_attr)
4248         window.AttributeOn(changd_attr);
4249       window.PutCStringTruncated(1, summary);
4250       if (changd_attr)
4251         window.AttributeOff(changd_attr);
4252     }
4253 
4254     if (highlight)
4255       window.AttributeOff(A_REVERSE);
4256 
4257     return true;
4258   }
4259 
4260   void DisplayRows(Window &window, std::vector<Row> &rows,
4261                    DisplayOptions &options) {
4262     // >   0x25B7
4263     // \/  0x25BD
4264 
4265     bool window_is_active = window.IsActive();
4266     for (auto &row : rows) {
4267       const bool last_child = row.parent && &rows[rows.size() - 1] == &row;
4268       // Save the row index in each Row structure
4269       row.row_idx = m_num_rows;
4270       if ((m_num_rows >= m_first_visible_row) &&
4271           ((m_num_rows - m_first_visible_row) <
4272            static_cast<size_t>(NumVisibleRows()))) {
4273         row.x = m_min_x;
4274         row.y = m_num_rows - m_first_visible_row + 1;
4275         if (DisplayRowObject(window, row, options,
4276                              window_is_active &&
4277                                  m_num_rows == m_selected_row_idx,
4278                              last_child)) {
4279           ++m_num_rows;
4280         } else {
4281           row.x = 0;
4282           row.y = 0;
4283         }
4284       } else {
4285         row.x = 0;
4286         row.y = 0;
4287         ++m_num_rows;
4288       }
4289 
4290       auto &children = row.GetChildren();
4291       if (row.expanded && !children.empty()) {
4292         DisplayRows(window, children, options);
4293       }
4294     }
4295   }
4296 
4297   int CalculateTotalNumberRows(std::vector<Row> &rows) {
4298     int row_count = 0;
4299     for (auto &row : rows) {
4300       ++row_count;
4301       if (row.expanded)
4302         row_count += CalculateTotalNumberRows(row.GetChildren());
4303     }
4304     return row_count;
4305   }
4306 
4307   static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) {
4308     for (auto &row : rows) {
4309       if (row_index == 0)
4310         return &row;
4311       else {
4312         --row_index;
4313         auto &children = row.GetChildren();
4314         if (row.expanded && !children.empty()) {
4315           Row *result = GetRowForRowIndexImpl(children, row_index);
4316           if (result)
4317             return result;
4318         }
4319       }
4320     }
4321     return nullptr;
4322   }
4323 
4324   Row *GetRowForRowIndex(size_t row_index) {
4325     return GetRowForRowIndexImpl(m_rows, row_index);
4326   }
4327 
4328   int NumVisibleRows() const { return m_max_y - m_min_y; }
4329 
4330   static DisplayOptions g_options;
4331 };
4332 
4333 class FrameVariablesWindowDelegate : public ValueObjectListDelegate {
4334 public:
4335   FrameVariablesWindowDelegate(Debugger &debugger)
4336       : ValueObjectListDelegate(), m_debugger(debugger),
4337         m_frame_block(nullptr) {}
4338 
4339   ~FrameVariablesWindowDelegate() override = default;
4340 
4341   const char *WindowDelegateGetHelpText() override {
4342     return "Frame variable window keyboard shortcuts:";
4343   }
4344 
4345   bool WindowDelegateDraw(Window &window, bool force) override {
4346     ExecutionContext exe_ctx(
4347         m_debugger.GetCommandInterpreter().GetExecutionContext());
4348     Process *process = exe_ctx.GetProcessPtr();
4349     Block *frame_block = nullptr;
4350     StackFrame *frame = nullptr;
4351 
4352     if (process) {
4353       StateType state = process->GetState();
4354       if (StateIsStoppedState(state, true)) {
4355         frame = exe_ctx.GetFramePtr();
4356         if (frame)
4357           frame_block = frame->GetFrameBlock();
4358       } else if (StateIsRunningState(state)) {
4359         return true; // Don't do any updating when we are running
4360       }
4361     }
4362 
4363     ValueObjectList local_values;
4364     if (frame_block) {
4365       // Only update the variables if they have changed
4366       if (m_frame_block != frame_block) {
4367         m_frame_block = frame_block;
4368 
4369         VariableList *locals = frame->GetVariableList(true);
4370         if (locals) {
4371           const DynamicValueType use_dynamic = eDynamicDontRunTarget;
4372           for (const VariableSP &local_sp : *locals) {
4373             ValueObjectSP value_sp =
4374                 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic);
4375             if (value_sp) {
4376               ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue();
4377               if (synthetic_value_sp)
4378                 local_values.Append(synthetic_value_sp);
4379               else
4380                 local_values.Append(value_sp);
4381             }
4382           }
4383           // Update the values
4384           SetValues(local_values);
4385         }
4386       }
4387     } else {
4388       m_frame_block = nullptr;
4389       // Update the values with an empty list if there is no frame
4390       SetValues(local_values);
4391     }
4392 
4393     return ValueObjectListDelegate::WindowDelegateDraw(window, force);
4394   }
4395 
4396 protected:
4397   Debugger &m_debugger;
4398   Block *m_frame_block;
4399 };
4400 
4401 class RegistersWindowDelegate : public ValueObjectListDelegate {
4402 public:
4403   RegistersWindowDelegate(Debugger &debugger)
4404       : ValueObjectListDelegate(), m_debugger(debugger) {}
4405 
4406   ~RegistersWindowDelegate() override = default;
4407 
4408   const char *WindowDelegateGetHelpText() override {
4409     return "Register window keyboard shortcuts:";
4410   }
4411 
4412   bool WindowDelegateDraw(Window &window, bool force) override {
4413     ExecutionContext exe_ctx(
4414         m_debugger.GetCommandInterpreter().GetExecutionContext());
4415     StackFrame *frame = exe_ctx.GetFramePtr();
4416 
4417     ValueObjectList value_list;
4418     if (frame) {
4419       if (frame->GetStackID() != m_stack_id) {
4420         m_stack_id = frame->GetStackID();
4421         RegisterContextSP reg_ctx(frame->GetRegisterContext());
4422         if (reg_ctx) {
4423           const uint32_t num_sets = reg_ctx->GetRegisterSetCount();
4424           for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) {
4425             value_list.Append(
4426                 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx));
4427           }
4428         }
4429         SetValues(value_list);
4430       }
4431     } else {
4432       Process *process = exe_ctx.GetProcessPtr();
4433       if (process && process->IsAlive())
4434         return true; // Don't do any updating if we are running
4435       else {
4436         // Update the values with an empty list if there is no process or the
4437         // process isn't alive anymore
4438         SetValues(value_list);
4439       }
4440     }
4441     return ValueObjectListDelegate::WindowDelegateDraw(window, force);
4442   }
4443 
4444 protected:
4445   Debugger &m_debugger;
4446   StackID m_stack_id;
4447 };
4448 
4449 static const char *CursesKeyToCString(int ch) {
4450   static char g_desc[32];
4451   if (ch >= KEY_F0 && ch < KEY_F0 + 64) {
4452     snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0);
4453     return g_desc;
4454   }
4455   switch (ch) {
4456   case KEY_DOWN:
4457     return "down";
4458   case KEY_UP:
4459     return "up";
4460   case KEY_LEFT:
4461     return "left";
4462   case KEY_RIGHT:
4463     return "right";
4464   case KEY_HOME:
4465     return "home";
4466   case KEY_BACKSPACE:
4467     return "backspace";
4468   case KEY_DL:
4469     return "delete-line";
4470   case KEY_IL:
4471     return "insert-line";
4472   case KEY_DC:
4473     return "delete-char";
4474   case KEY_IC:
4475     return "insert-char";
4476   case KEY_CLEAR:
4477     return "clear";
4478   case KEY_EOS:
4479     return "clear-to-eos";
4480   case KEY_EOL:
4481     return "clear-to-eol";
4482   case KEY_SF:
4483     return "scroll-forward";
4484   case KEY_SR:
4485     return "scroll-backward";
4486   case KEY_NPAGE:
4487     return "page-down";
4488   case KEY_PPAGE:
4489     return "page-up";
4490   case KEY_STAB:
4491     return "set-tab";
4492   case KEY_CTAB:
4493     return "clear-tab";
4494   case KEY_CATAB:
4495     return "clear-all-tabs";
4496   case KEY_ENTER:
4497     return "enter";
4498   case KEY_PRINT:
4499     return "print";
4500   case KEY_LL:
4501     return "lower-left key";
4502   case KEY_A1:
4503     return "upper left of keypad";
4504   case KEY_A3:
4505     return "upper right of keypad";
4506   case KEY_B2:
4507     return "center of keypad";
4508   case KEY_C1:
4509     return "lower left of keypad";
4510   case KEY_C3:
4511     return "lower right of keypad";
4512   case KEY_BTAB:
4513     return "back-tab key";
4514   case KEY_BEG:
4515     return "begin key";
4516   case KEY_CANCEL:
4517     return "cancel key";
4518   case KEY_CLOSE:
4519     return "close key";
4520   case KEY_COMMAND:
4521     return "command key";
4522   case KEY_COPY:
4523     return "copy key";
4524   case KEY_CREATE:
4525     return "create key";
4526   case KEY_END:
4527     return "end key";
4528   case KEY_EXIT:
4529     return "exit key";
4530   case KEY_FIND:
4531     return "find key";
4532   case KEY_HELP:
4533     return "help key";
4534   case KEY_MARK:
4535     return "mark key";
4536   case KEY_MESSAGE:
4537     return "message key";
4538   case KEY_MOVE:
4539     return "move key";
4540   case KEY_NEXT:
4541     return "next key";
4542   case KEY_OPEN:
4543     return "open key";
4544   case KEY_OPTIONS:
4545     return "options key";
4546   case KEY_PREVIOUS:
4547     return "previous key";
4548   case KEY_REDO:
4549     return "redo key";
4550   case KEY_REFERENCE:
4551     return "reference key";
4552   case KEY_REFRESH:
4553     return "refresh key";
4554   case KEY_REPLACE:
4555     return "replace key";
4556   case KEY_RESTART:
4557     return "restart key";
4558   case KEY_RESUME:
4559     return "resume key";
4560   case KEY_SAVE:
4561     return "save key";
4562   case KEY_SBEG:
4563     return "shifted begin key";
4564   case KEY_SCANCEL:
4565     return "shifted cancel key";
4566   case KEY_SCOMMAND:
4567     return "shifted command key";
4568   case KEY_SCOPY:
4569     return "shifted copy key";
4570   case KEY_SCREATE:
4571     return "shifted create key";
4572   case KEY_SDC:
4573     return "shifted delete-character key";
4574   case KEY_SDL:
4575     return "shifted delete-line key";
4576   case KEY_SELECT:
4577     return "select key";
4578   case KEY_SEND:
4579     return "shifted end key";
4580   case KEY_SEOL:
4581     return "shifted clear-to-end-of-line key";
4582   case KEY_SEXIT:
4583     return "shifted exit key";
4584   case KEY_SFIND:
4585     return "shifted find key";
4586   case KEY_SHELP:
4587     return "shifted help key";
4588   case KEY_SHOME:
4589     return "shifted home key";
4590   case KEY_SIC:
4591     return "shifted insert-character key";
4592   case KEY_SLEFT:
4593     return "shifted left-arrow key";
4594   case KEY_SMESSAGE:
4595     return "shifted message key";
4596   case KEY_SMOVE:
4597     return "shifted move key";
4598   case KEY_SNEXT:
4599     return "shifted next key";
4600   case KEY_SOPTIONS:
4601     return "shifted options key";
4602   case KEY_SPREVIOUS:
4603     return "shifted previous key";
4604   case KEY_SPRINT:
4605     return "shifted print key";
4606   case KEY_SREDO:
4607     return "shifted redo key";
4608   case KEY_SREPLACE:
4609     return "shifted replace key";
4610   case KEY_SRIGHT:
4611     return "shifted right-arrow key";
4612   case KEY_SRSUME:
4613     return "shifted resume key";
4614   case KEY_SSAVE:
4615     return "shifted save key";
4616   case KEY_SSUSPEND:
4617     return "shifted suspend key";
4618   case KEY_SUNDO:
4619     return "shifted undo key";
4620   case KEY_SUSPEND:
4621     return "suspend key";
4622   case KEY_UNDO:
4623     return "undo key";
4624   case KEY_MOUSE:
4625     return "Mouse event has occurred";
4626   case KEY_RESIZE:
4627     return "Terminal resize event";
4628 #ifdef KEY_EVENT
4629   case KEY_EVENT:
4630     return "We were interrupted by an event";
4631 #endif
4632   case KEY_RETURN:
4633     return "return";
4634   case ' ':
4635     return "space";
4636   case '\t':
4637     return "tab";
4638   case KEY_ESCAPE:
4639     return "escape";
4640   default:
4641     if (llvm::isPrint(ch))
4642       snprintf(g_desc, sizeof(g_desc), "%c", ch);
4643     else
4644       snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch);
4645     return g_desc;
4646   }
4647   return nullptr;
4648 }
4649 
4650 HelpDialogDelegate::HelpDialogDelegate(const char *text,
4651                                        KeyHelp *key_help_array)
4652     : m_text(), m_first_visible_line(0) {
4653   if (text && text[0]) {
4654     m_text.SplitIntoLines(text);
4655     m_text.AppendString("");
4656   }
4657   if (key_help_array) {
4658     for (KeyHelp *key = key_help_array; key->ch; ++key) {
4659       StreamString key_description;
4660       key_description.Printf("%10s - %s", CursesKeyToCString(key->ch),
4661                              key->description);
4662       m_text.AppendString(key_description.GetString());
4663     }
4664   }
4665 }
4666 
4667 HelpDialogDelegate::~HelpDialogDelegate() = default;
4668 
4669 bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) {
4670   window.Erase();
4671   const int window_height = window.GetHeight();
4672   int x = 2;
4673   int y = 1;
4674   const int min_y = y;
4675   const int max_y = window_height - 1 - y;
4676   const size_t num_visible_lines = max_y - min_y + 1;
4677   const size_t num_lines = m_text.GetSize();
4678   const char *bottom_message;
4679   if (num_lines <= num_visible_lines)
4680     bottom_message = "Press any key to exit";
4681   else
4682     bottom_message = "Use arrows to scroll, any other key to exit";
4683   window.DrawTitleBox(window.GetName(), bottom_message);
4684   while (y <= max_y) {
4685     window.MoveCursor(x, y);
4686     window.PutCStringTruncated(
4687         1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y));
4688     ++y;
4689   }
4690   return true;
4691 }
4692 
4693 HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window,
4694                                                               int key) {
4695   bool done = false;
4696   const size_t num_lines = m_text.GetSize();
4697   const size_t num_visible_lines = window.GetHeight() - 2;
4698 
4699   if (num_lines <= num_visible_lines) {
4700     done = true;
4701     // If we have all lines visible and don't need scrolling, then any key
4702     // press will cause us to exit
4703   } else {
4704     switch (key) {
4705     case KEY_UP:
4706       if (m_first_visible_line > 0)
4707         --m_first_visible_line;
4708       break;
4709 
4710     case KEY_DOWN:
4711       if (m_first_visible_line + num_visible_lines < num_lines)
4712         ++m_first_visible_line;
4713       break;
4714 
4715     case KEY_PPAGE:
4716     case ',':
4717       if (m_first_visible_line > 0) {
4718         if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines)
4719           m_first_visible_line -= num_visible_lines;
4720         else
4721           m_first_visible_line = 0;
4722       }
4723       break;
4724 
4725     case KEY_NPAGE:
4726     case '.':
4727       if (m_first_visible_line + num_visible_lines < num_lines) {
4728         m_first_visible_line += num_visible_lines;
4729         if (static_cast<size_t>(m_first_visible_line) > num_lines)
4730           m_first_visible_line = num_lines - num_visible_lines;
4731       }
4732       break;
4733 
4734     default:
4735       done = true;
4736       break;
4737     }
4738   }
4739   if (done)
4740     window.GetParent()->RemoveSubWindow(&window);
4741   return eKeyHandled;
4742 }
4743 
4744 class ApplicationDelegate : public WindowDelegate, public MenuDelegate {
4745 public:
4746   enum {
4747     eMenuID_LLDB = 1,
4748     eMenuID_LLDBAbout,
4749     eMenuID_LLDBExit,
4750 
4751     eMenuID_Target,
4752     eMenuID_TargetCreate,
4753     eMenuID_TargetDelete,
4754 
4755     eMenuID_Process,
4756     eMenuID_ProcessAttach,
4757     eMenuID_ProcessDetachResume,
4758     eMenuID_ProcessDetachSuspended,
4759     eMenuID_ProcessLaunch,
4760     eMenuID_ProcessContinue,
4761     eMenuID_ProcessHalt,
4762     eMenuID_ProcessKill,
4763 
4764     eMenuID_Thread,
4765     eMenuID_ThreadStepIn,
4766     eMenuID_ThreadStepOver,
4767     eMenuID_ThreadStepOut,
4768 
4769     eMenuID_View,
4770     eMenuID_ViewBacktrace,
4771     eMenuID_ViewRegisters,
4772     eMenuID_ViewSource,
4773     eMenuID_ViewVariables,
4774 
4775     eMenuID_Help,
4776     eMenuID_HelpGUIHelp
4777   };
4778 
4779   ApplicationDelegate(Application &app, Debugger &debugger)
4780       : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {}
4781 
4782   ~ApplicationDelegate() override = default;
4783 
4784   bool WindowDelegateDraw(Window &window, bool force) override {
4785     return false; // Drawing not handled, let standard window drawing happen
4786   }
4787 
4788   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
4789     switch (key) {
4790     case '\t':
4791       window.SelectNextWindowAsActive();
4792       return eKeyHandled;
4793 
4794     case KEY_SHIFT_TAB:
4795       window.SelectPreviousWindowAsActive();
4796       return eKeyHandled;
4797 
4798     case 'h':
4799       window.CreateHelpSubwindow();
4800       return eKeyHandled;
4801 
4802     case KEY_ESCAPE:
4803       return eQuitApplication;
4804 
4805     default:
4806       break;
4807     }
4808     return eKeyNotHandled;
4809   }
4810 
4811   const char *WindowDelegateGetHelpText() override {
4812     return "Welcome to the LLDB curses GUI.\n\n"
4813            "Press the TAB key to change the selected view.\n"
4814            "Each view has its own keyboard shortcuts, press 'h' to open a "
4815            "dialog to display them.\n\n"
4816            "Common key bindings for all views:";
4817   }
4818 
4819   KeyHelp *WindowDelegateGetKeyHelp() override {
4820     static curses::KeyHelp g_source_view_key_help[] = {
4821         {'\t', "Select next view"},
4822         {KEY_BTAB, "Select previous view"},
4823         {'h', "Show help dialog with view specific key bindings"},
4824         {',', "Page up"},
4825         {'.', "Page down"},
4826         {KEY_UP, "Select previous"},
4827         {KEY_DOWN, "Select next"},
4828         {KEY_LEFT, "Unexpand or select parent"},
4829         {KEY_RIGHT, "Expand"},
4830         {KEY_PPAGE, "Page up"},
4831         {KEY_NPAGE, "Page down"},
4832         {'\0', nullptr}};
4833     return g_source_view_key_help;
4834   }
4835 
4836   MenuActionResult MenuDelegateAction(Menu &menu) override {
4837     switch (menu.GetIdentifier()) {
4838     case eMenuID_ThreadStepIn: {
4839       ExecutionContext exe_ctx =
4840           m_debugger.GetCommandInterpreter().GetExecutionContext();
4841       if (exe_ctx.HasThreadScope()) {
4842         Process *process = exe_ctx.GetProcessPtr();
4843         if (process && process->IsAlive() &&
4844             StateIsStoppedState(process->GetState(), true))
4845           exe_ctx.GetThreadRef().StepIn(true);
4846       }
4847     }
4848       return MenuActionResult::Handled;
4849 
4850     case eMenuID_ThreadStepOut: {
4851       ExecutionContext exe_ctx =
4852           m_debugger.GetCommandInterpreter().GetExecutionContext();
4853       if (exe_ctx.HasThreadScope()) {
4854         Process *process = exe_ctx.GetProcessPtr();
4855         if (process && process->IsAlive() &&
4856             StateIsStoppedState(process->GetState(), true))
4857           exe_ctx.GetThreadRef().StepOut();
4858       }
4859     }
4860       return MenuActionResult::Handled;
4861 
4862     case eMenuID_ThreadStepOver: {
4863       ExecutionContext exe_ctx =
4864           m_debugger.GetCommandInterpreter().GetExecutionContext();
4865       if (exe_ctx.HasThreadScope()) {
4866         Process *process = exe_ctx.GetProcessPtr();
4867         if (process && process->IsAlive() &&
4868             StateIsStoppedState(process->GetState(), true))
4869           exe_ctx.GetThreadRef().StepOver(true);
4870       }
4871     }
4872       return MenuActionResult::Handled;
4873 
4874     case eMenuID_ProcessAttach: {
4875       WindowSP main_window_sp = m_app.GetMainWindow();
4876       FormDelegateSP form_delegate_sp = FormDelegateSP(
4877           new ProcessAttachFormDelegate(m_debugger, main_window_sp));
4878       Rect bounds = main_window_sp->GetCenteredRect(80, 22);
4879       WindowSP form_window_sp = main_window_sp->CreateSubWindow(
4880           form_delegate_sp->GetName().c_str(), bounds, true);
4881       WindowDelegateSP window_delegate_sp =
4882           WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
4883       form_window_sp->SetDelegate(window_delegate_sp);
4884       return MenuActionResult::Handled;
4885     }
4886 
4887     case eMenuID_ProcessContinue: {
4888       ExecutionContext exe_ctx =
4889           m_debugger.GetCommandInterpreter().GetExecutionContext();
4890       if (exe_ctx.HasProcessScope()) {
4891         Process *process = exe_ctx.GetProcessPtr();
4892         if (process && process->IsAlive() &&
4893             StateIsStoppedState(process->GetState(), true))
4894           process->Resume();
4895       }
4896     }
4897       return MenuActionResult::Handled;
4898 
4899     case eMenuID_ProcessKill: {
4900       ExecutionContext exe_ctx =
4901           m_debugger.GetCommandInterpreter().GetExecutionContext();
4902       if (exe_ctx.HasProcessScope()) {
4903         Process *process = exe_ctx.GetProcessPtr();
4904         if (process && process->IsAlive())
4905           process->Destroy(false);
4906       }
4907     }
4908       return MenuActionResult::Handled;
4909 
4910     case eMenuID_ProcessHalt: {
4911       ExecutionContext exe_ctx =
4912           m_debugger.GetCommandInterpreter().GetExecutionContext();
4913       if (exe_ctx.HasProcessScope()) {
4914         Process *process = exe_ctx.GetProcessPtr();
4915         if (process && process->IsAlive())
4916           process->Halt();
4917       }
4918     }
4919       return MenuActionResult::Handled;
4920 
4921     case eMenuID_ProcessDetachResume:
4922     case eMenuID_ProcessDetachSuspended: {
4923       ExecutionContext exe_ctx =
4924           m_debugger.GetCommandInterpreter().GetExecutionContext();
4925       if (exe_ctx.HasProcessScope()) {
4926         Process *process = exe_ctx.GetProcessPtr();
4927         if (process && process->IsAlive())
4928           process->Detach(menu.GetIdentifier() ==
4929                           eMenuID_ProcessDetachSuspended);
4930       }
4931     }
4932       return MenuActionResult::Handled;
4933 
4934     case eMenuID_Process: {
4935       // Populate the menu with all of the threads if the process is stopped
4936       // when the Process menu gets selected and is about to display its
4937       // submenu.
4938       Menus &submenus = menu.GetSubmenus();
4939       ExecutionContext exe_ctx =
4940           m_debugger.GetCommandInterpreter().GetExecutionContext();
4941       Process *process = exe_ctx.GetProcessPtr();
4942       if (process && process->IsAlive() &&
4943           StateIsStoppedState(process->GetState(), true)) {
4944         if (submenus.size() == 7)
4945           menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
4946         else if (submenus.size() > 8)
4947           submenus.erase(submenus.begin() + 8, submenus.end());
4948 
4949         ThreadList &threads = process->GetThreadList();
4950         std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
4951         size_t num_threads = threads.GetSize();
4952         for (size_t i = 0; i < num_threads; ++i) {
4953           ThreadSP thread_sp = threads.GetThreadAtIndex(i);
4954           char menu_char = '\0';
4955           if (i < 9)
4956             menu_char = '1' + i;
4957           StreamString thread_menu_title;
4958           thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID());
4959           const char *thread_name = thread_sp->GetName();
4960           if (thread_name && thread_name[0])
4961             thread_menu_title.Printf(" %s", thread_name);
4962           else {
4963             const char *queue_name = thread_sp->GetQueueName();
4964             if (queue_name && queue_name[0])
4965               thread_menu_title.Printf(" %s", queue_name);
4966           }
4967           menu.AddSubmenu(
4968               MenuSP(new Menu(thread_menu_title.GetString().str().c_str(),
4969                               nullptr, menu_char, thread_sp->GetID())));
4970         }
4971       } else if (submenus.size() > 7) {
4972         // Remove the separator and any other thread submenu items that were
4973         // previously added
4974         submenus.erase(submenus.begin() + 7, submenus.end());
4975       }
4976       // Since we are adding and removing items we need to recalculate the name
4977       // lengths
4978       menu.RecalculateNameLengths();
4979     }
4980       return MenuActionResult::Handled;
4981 
4982     case eMenuID_ViewVariables: {
4983       WindowSP main_window_sp = m_app.GetMainWindow();
4984       WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
4985       WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
4986       WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
4987       const Rect source_bounds = source_window_sp->GetBounds();
4988 
4989       if (variables_window_sp) {
4990         const Rect variables_bounds = variables_window_sp->GetBounds();
4991 
4992         main_window_sp->RemoveSubWindow(variables_window_sp.get());
4993 
4994         if (registers_window_sp) {
4995           // We have a registers window, so give all the area back to the
4996           // registers window
4997           Rect registers_bounds = variables_bounds;
4998           registers_bounds.size.width = source_bounds.size.width;
4999           registers_window_sp->SetBounds(registers_bounds);
5000         } else {
5001           // We have no registers window showing so give the bottom area back
5002           // to the source view
5003           source_window_sp->Resize(source_bounds.size.width,
5004                                    source_bounds.size.height +
5005                                        variables_bounds.size.height);
5006         }
5007       } else {
5008         Rect new_variables_rect;
5009         if (registers_window_sp) {
5010           // We have a registers window so split the area of the registers
5011           // window into two columns where the left hand side will be the
5012           // variables and the right hand side will be the registers
5013           const Rect variables_bounds = registers_window_sp->GetBounds();
5014           Rect new_registers_rect;
5015           variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect,
5016                                                    new_registers_rect);
5017           registers_window_sp->SetBounds(new_registers_rect);
5018         } else {
5019           // No registers window, grab the bottom part of the source window
5020           Rect new_source_rect;
5021           source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
5022                                                   new_variables_rect);
5023           source_window_sp->SetBounds(new_source_rect);
5024         }
5025         WindowSP new_window_sp = main_window_sp->CreateSubWindow(
5026             "Variables", new_variables_rect, false);
5027         new_window_sp->SetDelegate(
5028             WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
5029       }
5030       touchwin(stdscr);
5031     }
5032       return MenuActionResult::Handled;
5033 
5034     case eMenuID_ViewRegisters: {
5035       WindowSP main_window_sp = m_app.GetMainWindow();
5036       WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
5037       WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
5038       WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
5039       const Rect source_bounds = source_window_sp->GetBounds();
5040 
5041       if (registers_window_sp) {
5042         if (variables_window_sp) {
5043           const Rect variables_bounds = variables_window_sp->GetBounds();
5044 
5045           // We have a variables window, so give all the area back to the
5046           // variables window
5047           variables_window_sp->Resize(variables_bounds.size.width +
5048                                           registers_window_sp->GetWidth(),
5049                                       variables_bounds.size.height);
5050         } else {
5051           // We have no variables window showing so give the bottom area back
5052           // to the source view
5053           source_window_sp->Resize(source_bounds.size.width,
5054                                    source_bounds.size.height +
5055                                        registers_window_sp->GetHeight());
5056         }
5057         main_window_sp->RemoveSubWindow(registers_window_sp.get());
5058       } else {
5059         Rect new_regs_rect;
5060         if (variables_window_sp) {
5061           // We have a variables window, split it into two columns where the
5062           // left hand side will be the variables and the right hand side will
5063           // be the registers
5064           const Rect variables_bounds = variables_window_sp->GetBounds();
5065           Rect new_vars_rect;
5066           variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect,
5067                                                    new_regs_rect);
5068           variables_window_sp->SetBounds(new_vars_rect);
5069         } else {
5070           // No variables window, grab the bottom part of the source window
5071           Rect new_source_rect;
5072           source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
5073                                                   new_regs_rect);
5074           source_window_sp->SetBounds(new_source_rect);
5075         }
5076         WindowSP new_window_sp =
5077             main_window_sp->CreateSubWindow("Registers", new_regs_rect, false);
5078         new_window_sp->SetDelegate(
5079             WindowDelegateSP(new RegistersWindowDelegate(m_debugger)));
5080       }
5081       touchwin(stdscr);
5082     }
5083       return MenuActionResult::Handled;
5084 
5085     case eMenuID_HelpGUIHelp:
5086       m_app.GetMainWindow()->CreateHelpSubwindow();
5087       return MenuActionResult::Handled;
5088 
5089     default:
5090       break;
5091     }
5092 
5093     return MenuActionResult::NotHandled;
5094   }
5095 
5096 protected:
5097   Application &m_app;
5098   Debugger &m_debugger;
5099 };
5100 
5101 class StatusBarWindowDelegate : public WindowDelegate {
5102 public:
5103   StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) {
5104     FormatEntity::Parse("Thread: ${thread.id%tid}", m_format);
5105   }
5106 
5107   ~StatusBarWindowDelegate() override = default;
5108 
5109   bool WindowDelegateDraw(Window &window, bool force) override {
5110     ExecutionContext exe_ctx =
5111         m_debugger.GetCommandInterpreter().GetExecutionContext();
5112     Process *process = exe_ctx.GetProcessPtr();
5113     Thread *thread = exe_ctx.GetThreadPtr();
5114     StackFrame *frame = exe_ctx.GetFramePtr();
5115     window.Erase();
5116     window.SetBackground(BlackOnWhite);
5117     window.MoveCursor(0, 0);
5118     if (process) {
5119       const StateType state = process->GetState();
5120       window.Printf("Process: %5" PRIu64 " %10s", process->GetID(),
5121                     StateAsCString(state));
5122 
5123       if (StateIsStoppedState(state, true)) {
5124         StreamString strm;
5125         if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx,
5126                                            nullptr, nullptr, false, false)) {
5127           window.MoveCursor(40, 0);
5128           window.PutCStringTruncated(1, strm.GetString().str().c_str());
5129         }
5130 
5131         window.MoveCursor(60, 0);
5132         if (frame)
5133           window.Printf("Frame: %3u  PC = 0x%16.16" PRIx64,
5134                         frame->GetFrameIndex(),
5135                         frame->GetFrameCodeAddress().GetOpcodeLoadAddress(
5136                             exe_ctx.GetTargetPtr()));
5137       } else if (state == eStateExited) {
5138         const char *exit_desc = process->GetExitDescription();
5139         const int exit_status = process->GetExitStatus();
5140         if (exit_desc && exit_desc[0])
5141           window.Printf(" with status = %i (%s)", exit_status, exit_desc);
5142         else
5143           window.Printf(" with status = %i", exit_status);
5144       }
5145     }
5146     return true;
5147   }
5148 
5149 protected:
5150   Debugger &m_debugger;
5151   FormatEntity::Entry m_format;
5152 };
5153 
5154 class SourceFileWindowDelegate : public WindowDelegate {
5155 public:
5156   SourceFileWindowDelegate(Debugger &debugger)
5157       : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(),
5158         m_disassembly_scope(nullptr), m_disassembly_sp(), m_disassembly_range(),
5159         m_title(), m_tid(LLDB_INVALID_THREAD_ID), m_line_width(4),
5160         m_selected_line(0), m_pc_line(0), m_stop_id(0), m_frame_idx(UINT32_MAX),
5161         m_first_visible_line(0), m_first_visible_column(0), m_min_x(0),
5162         m_min_y(0), m_max_x(0), m_max_y(0) {}
5163 
5164   ~SourceFileWindowDelegate() override = default;
5165 
5166   void Update(const SymbolContext &sc) { m_sc = sc; }
5167 
5168   uint32_t NumVisibleLines() const { return m_max_y - m_min_y; }
5169 
5170   const char *WindowDelegateGetHelpText() override {
5171     return "Source/Disassembly window keyboard shortcuts:";
5172   }
5173 
5174   KeyHelp *WindowDelegateGetKeyHelp() override {
5175     static curses::KeyHelp g_source_view_key_help[] = {
5176         {KEY_RETURN, "Run to selected line with one shot breakpoint"},
5177         {KEY_UP, "Select previous source line"},
5178         {KEY_DOWN, "Select next source line"},
5179         {KEY_LEFT, "Scroll to the left"},
5180         {KEY_RIGHT, "Scroll to the right"},
5181         {KEY_PPAGE, "Page up"},
5182         {KEY_NPAGE, "Page down"},
5183         {'b', "Set breakpoint on selected source/disassembly line"},
5184         {'c', "Continue process"},
5185         {'D', "Detach with process suspended"},
5186         {'h', "Show help dialog"},
5187         {'n', "Step over (source line)"},
5188         {'N', "Step over (single instruction)"},
5189         {'f', "Step out (finish)"},
5190         {'s', "Step in (source line)"},
5191         {'S', "Step in (single instruction)"},
5192         {'u', "Frame up"},
5193         {'d', "Frame down"},
5194         {',', "Page up"},
5195         {'.', "Page down"},
5196         {'\0', nullptr}};
5197     return g_source_view_key_help;
5198   }
5199 
5200   bool WindowDelegateDraw(Window &window, bool force) override {
5201     ExecutionContext exe_ctx =
5202         m_debugger.GetCommandInterpreter().GetExecutionContext();
5203     Process *process = exe_ctx.GetProcessPtr();
5204     Thread *thread = nullptr;
5205 
5206     bool update_location = false;
5207     if (process) {
5208       StateType state = process->GetState();
5209       if (StateIsStoppedState(state, true)) {
5210         // We are stopped, so it is ok to
5211         update_location = true;
5212       }
5213     }
5214 
5215     m_min_x = 1;
5216     m_min_y = 2;
5217     m_max_x = window.GetMaxX() - 1;
5218     m_max_y = window.GetMaxY() - 1;
5219 
5220     const uint32_t num_visible_lines = NumVisibleLines();
5221     StackFrameSP frame_sp;
5222     bool set_selected_line_to_pc = false;
5223 
5224     if (update_location) {
5225       const bool process_alive = process ? process->IsAlive() : false;
5226       bool thread_changed = false;
5227       if (process_alive) {
5228         thread = exe_ctx.GetThreadPtr();
5229         if (thread) {
5230           frame_sp = thread->GetSelectedFrame();
5231           auto tid = thread->GetID();
5232           thread_changed = tid != m_tid;
5233           m_tid = tid;
5234         } else {
5235           if (m_tid != LLDB_INVALID_THREAD_ID) {
5236             thread_changed = true;
5237             m_tid = LLDB_INVALID_THREAD_ID;
5238           }
5239         }
5240       }
5241       const uint32_t stop_id = process ? process->GetStopID() : 0;
5242       const bool stop_id_changed = stop_id != m_stop_id;
5243       bool frame_changed = false;
5244       m_stop_id = stop_id;
5245       m_title.Clear();
5246       if (frame_sp) {
5247         m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything);
5248         if (m_sc.module_sp) {
5249           m_title.Printf(
5250               "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString());
5251           ConstString func_name = m_sc.GetFunctionName();
5252           if (func_name)
5253             m_title.Printf("`%s", func_name.GetCString());
5254         }
5255         const uint32_t frame_idx = frame_sp->GetFrameIndex();
5256         frame_changed = frame_idx != m_frame_idx;
5257         m_frame_idx = frame_idx;
5258       } else {
5259         m_sc.Clear(true);
5260         frame_changed = m_frame_idx != UINT32_MAX;
5261         m_frame_idx = UINT32_MAX;
5262       }
5263 
5264       const bool context_changed =
5265           thread_changed || frame_changed || stop_id_changed;
5266 
5267       if (process_alive) {
5268         if (m_sc.line_entry.IsValid()) {
5269           m_pc_line = m_sc.line_entry.line;
5270           if (m_pc_line != UINT32_MAX)
5271             --m_pc_line; // Convert to zero based line number...
5272           // Update the selected line if the stop ID changed...
5273           if (context_changed)
5274             m_selected_line = m_pc_line;
5275 
5276           if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) {
5277             // Same file, nothing to do, we should either have the lines or not
5278             // (source file missing)
5279             if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) {
5280               if (m_selected_line >= m_first_visible_line + num_visible_lines)
5281                 m_first_visible_line = m_selected_line - 10;
5282             } else {
5283               if (m_selected_line > 10)
5284                 m_first_visible_line = m_selected_line - 10;
5285               else
5286                 m_first_visible_line = 0;
5287             }
5288           } else {
5289             // File changed, set selected line to the line with the PC
5290             m_selected_line = m_pc_line;
5291             m_file_sp =
5292                 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file);
5293             if (m_file_sp) {
5294               const size_t num_lines = m_file_sp->GetNumLines();
5295               m_line_width = 1;
5296               for (size_t n = num_lines; n >= 10; n = n / 10)
5297                 ++m_line_width;
5298 
5299               if (num_lines < num_visible_lines ||
5300                   m_selected_line < num_visible_lines)
5301                 m_first_visible_line = 0;
5302               else
5303                 m_first_visible_line = m_selected_line - 10;
5304             }
5305           }
5306         } else {
5307           m_file_sp.reset();
5308         }
5309 
5310         if (!m_file_sp || m_file_sp->GetNumLines() == 0) {
5311           // Show disassembly
5312           bool prefer_file_cache = false;
5313           if (m_sc.function) {
5314             if (m_disassembly_scope != m_sc.function) {
5315               m_disassembly_scope = m_sc.function;
5316               m_disassembly_sp = m_sc.function->GetInstructions(
5317                   exe_ctx, nullptr, !prefer_file_cache);
5318               if (m_disassembly_sp) {
5319                 set_selected_line_to_pc = true;
5320                 m_disassembly_range = m_sc.function->GetAddressRange();
5321               } else {
5322                 m_disassembly_range.Clear();
5323               }
5324             } else {
5325               set_selected_line_to_pc = context_changed;
5326             }
5327           } else if (m_sc.symbol) {
5328             if (m_disassembly_scope != m_sc.symbol) {
5329               m_disassembly_scope = m_sc.symbol;
5330               m_disassembly_sp = m_sc.symbol->GetInstructions(
5331                   exe_ctx, nullptr, prefer_file_cache);
5332               if (m_disassembly_sp) {
5333                 set_selected_line_to_pc = true;
5334                 m_disassembly_range.GetBaseAddress() =
5335                     m_sc.symbol->GetAddress();
5336                 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize());
5337               } else {
5338                 m_disassembly_range.Clear();
5339               }
5340             } else {
5341               set_selected_line_to_pc = context_changed;
5342             }
5343           }
5344         }
5345       } else {
5346         m_pc_line = UINT32_MAX;
5347       }
5348     }
5349 
5350     const int window_width = window.GetWidth();
5351     window.Erase();
5352     window.DrawTitleBox("Sources");
5353     if (!m_title.GetString().empty()) {
5354       window.AttributeOn(A_REVERSE);
5355       window.MoveCursor(1, 1);
5356       window.PutChar(' ');
5357       window.PutCStringTruncated(1, m_title.GetString().str().c_str());
5358       int x = window.GetCursorX();
5359       if (x < window_width - 1) {
5360         window.Printf("%*s", window_width - x - 1, "");
5361       }
5362       window.AttributeOff(A_REVERSE);
5363     }
5364 
5365     Target *target = exe_ctx.GetTargetPtr();
5366     const size_t num_source_lines = GetNumSourceLines();
5367     if (num_source_lines > 0) {
5368       // Display source
5369       BreakpointLines bp_lines;
5370       if (target) {
5371         BreakpointList &bp_list = target->GetBreakpointList();
5372         const size_t num_bps = bp_list.GetSize();
5373         for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
5374           BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
5375           const size_t num_bps_locs = bp_sp->GetNumLocations();
5376           for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
5377             BreakpointLocationSP bp_loc_sp =
5378                 bp_sp->GetLocationAtIndex(bp_loc_idx);
5379             LineEntry bp_loc_line_entry;
5380             if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
5381                     bp_loc_line_entry)) {
5382               if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) {
5383                 bp_lines.insert(bp_loc_line_entry.line);
5384               }
5385             }
5386           }
5387         }
5388       }
5389 
5390       const attr_t selected_highlight_attr = A_REVERSE;
5391       const attr_t pc_highlight_attr = COLOR_PAIR(BlackOnBlue);
5392 
5393       for (size_t i = 0; i < num_visible_lines; ++i) {
5394         const uint32_t curr_line = m_first_visible_line + i;
5395         if (curr_line < num_source_lines) {
5396           const int line_y = m_min_y + i;
5397           window.MoveCursor(1, line_y);
5398           const bool is_pc_line = curr_line == m_pc_line;
5399           const bool line_is_selected = m_selected_line == curr_line;
5400           // Highlight the line as the PC line first, then if the selected line
5401           // isn't the same as the PC line, highlight it differently
5402           attr_t highlight_attr = 0;
5403           attr_t bp_attr = 0;
5404           if (is_pc_line)
5405             highlight_attr = pc_highlight_attr;
5406           else if (line_is_selected)
5407             highlight_attr = selected_highlight_attr;
5408 
5409           if (bp_lines.find(curr_line + 1) != bp_lines.end())
5410             bp_attr = COLOR_PAIR(BlackOnWhite);
5411 
5412           if (bp_attr)
5413             window.AttributeOn(bp_attr);
5414 
5415           window.Printf(" %*u ", m_line_width, curr_line + 1);
5416 
5417           if (bp_attr)
5418             window.AttributeOff(bp_attr);
5419 
5420           window.PutChar(ACS_VLINE);
5421           // Mark the line with the PC with a diamond
5422           if (is_pc_line)
5423             window.PutChar(ACS_DIAMOND);
5424           else
5425             window.PutChar(' ');
5426 
5427           if (highlight_attr)
5428             window.AttributeOn(highlight_attr);
5429 
5430           StreamString lineStream;
5431           m_file_sp->DisplaySourceLines(curr_line + 1, {}, 0, 0, &lineStream);
5432           StringRef line = lineStream.GetString();
5433           if (line.endswith("\n"))
5434             line = line.drop_back();
5435           bool wasWritten = window.OutputColoredStringTruncated(
5436               1, line, m_first_visible_column, line_is_selected);
5437           if (line_is_selected && !wasWritten) {
5438             // Draw an empty space to show the selected line if empty,
5439             // or draw '<' if nothing is visible because of scrolling too much
5440             // to the right.
5441             window.PutCStringTruncated(
5442                 1, line.empty() && m_first_visible_column == 0 ? " " : "<");
5443           }
5444 
5445           if (is_pc_line && frame_sp &&
5446               frame_sp->GetConcreteFrameIndex() == 0) {
5447             StopInfoSP stop_info_sp;
5448             if (thread)
5449               stop_info_sp = thread->GetStopInfo();
5450             if (stop_info_sp) {
5451               const char *stop_description = stop_info_sp->GetDescription();
5452               if (stop_description && stop_description[0]) {
5453                 size_t stop_description_len = strlen(stop_description);
5454                 int desc_x = window_width - stop_description_len - 16;
5455                 if (desc_x - window.GetCursorX() > 0)
5456                   window.Printf("%*s", desc_x - window.GetCursorX(), "");
5457                 window.MoveCursor(window_width - stop_description_len - 16,
5458                                   line_y);
5459                 const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue);
5460                 window.AttributeOn(stop_reason_attr);
5461                 window.PrintfTruncated(1, " <<< Thread %u: %s ",
5462                                        thread->GetIndexID(), stop_description);
5463                 window.AttributeOff(stop_reason_attr);
5464               }
5465             } else {
5466               window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
5467             }
5468           }
5469           if (highlight_attr)
5470             window.AttributeOff(highlight_attr);
5471         } else {
5472           break;
5473         }
5474       }
5475     } else {
5476       size_t num_disassembly_lines = GetNumDisassemblyLines();
5477       if (num_disassembly_lines > 0) {
5478         // Display disassembly
5479         BreakpointAddrs bp_file_addrs;
5480         Target *target = exe_ctx.GetTargetPtr();
5481         if (target) {
5482           BreakpointList &bp_list = target->GetBreakpointList();
5483           const size_t num_bps = bp_list.GetSize();
5484           for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
5485             BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
5486             const size_t num_bps_locs = bp_sp->GetNumLocations();
5487             for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs;
5488                  ++bp_loc_idx) {
5489               BreakpointLocationSP bp_loc_sp =
5490                   bp_sp->GetLocationAtIndex(bp_loc_idx);
5491               LineEntry bp_loc_line_entry;
5492               const lldb::addr_t file_addr =
5493                   bp_loc_sp->GetAddress().GetFileAddress();
5494               if (file_addr != LLDB_INVALID_ADDRESS) {
5495                 if (m_disassembly_range.ContainsFileAddress(file_addr))
5496                   bp_file_addrs.insert(file_addr);
5497               }
5498             }
5499           }
5500         }
5501 
5502         const attr_t selected_highlight_attr = A_REVERSE;
5503         const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue);
5504 
5505         StreamString strm;
5506 
5507         InstructionList &insts = m_disassembly_sp->GetInstructionList();
5508         Address pc_address;
5509 
5510         if (frame_sp)
5511           pc_address = frame_sp->GetFrameCodeAddress();
5512         const uint32_t pc_idx =
5513             pc_address.IsValid()
5514                 ? insts.GetIndexOfInstructionAtAddress(pc_address)
5515                 : UINT32_MAX;
5516         if (set_selected_line_to_pc) {
5517           m_selected_line = pc_idx;
5518         }
5519 
5520         const uint32_t non_visible_pc_offset = (num_visible_lines / 5);
5521         if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines)
5522           m_first_visible_line = 0;
5523 
5524         if (pc_idx < num_disassembly_lines) {
5525           if (pc_idx < static_cast<uint32_t>(m_first_visible_line) ||
5526               pc_idx >= m_first_visible_line + num_visible_lines)
5527             m_first_visible_line = pc_idx - non_visible_pc_offset;
5528         }
5529 
5530         for (size_t i = 0; i < num_visible_lines; ++i) {
5531           const uint32_t inst_idx = m_first_visible_line + i;
5532           Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get();
5533           if (!inst)
5534             break;
5535 
5536           const int line_y = m_min_y + i;
5537           window.MoveCursor(1, line_y);
5538           const bool is_pc_line = frame_sp && inst_idx == pc_idx;
5539           const bool line_is_selected = m_selected_line == inst_idx;
5540           // Highlight the line as the PC line first, then if the selected line
5541           // isn't the same as the PC line, highlight it differently
5542           attr_t highlight_attr = 0;
5543           attr_t bp_attr = 0;
5544           if (is_pc_line)
5545             highlight_attr = pc_highlight_attr;
5546           else if (line_is_selected)
5547             highlight_attr = selected_highlight_attr;
5548 
5549           if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) !=
5550               bp_file_addrs.end())
5551             bp_attr = COLOR_PAIR(BlackOnWhite);
5552 
5553           if (bp_attr)
5554             window.AttributeOn(bp_attr);
5555 
5556           window.Printf(" 0x%16.16llx ",
5557                         static_cast<unsigned long long>(
5558                             inst->GetAddress().GetLoadAddress(target)));
5559 
5560           if (bp_attr)
5561             window.AttributeOff(bp_attr);
5562 
5563           window.PutChar(ACS_VLINE);
5564           // Mark the line with the PC with a diamond
5565           if (is_pc_line)
5566             window.PutChar(ACS_DIAMOND);
5567           else
5568             window.PutChar(' ');
5569 
5570           if (highlight_attr)
5571             window.AttributeOn(highlight_attr);
5572 
5573           const char *mnemonic = inst->GetMnemonic(&exe_ctx);
5574           const char *operands = inst->GetOperands(&exe_ctx);
5575           const char *comment = inst->GetComment(&exe_ctx);
5576 
5577           if (mnemonic != nullptr && mnemonic[0] == '\0')
5578             mnemonic = nullptr;
5579           if (operands != nullptr && operands[0] == '\0')
5580             operands = nullptr;
5581           if (comment != nullptr && comment[0] == '\0')
5582             comment = nullptr;
5583 
5584           strm.Clear();
5585 
5586           if (mnemonic != nullptr && operands != nullptr && comment != nullptr)
5587             strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment);
5588           else if (mnemonic != nullptr && operands != nullptr)
5589             strm.Printf("%-8s %s", mnemonic, operands);
5590           else if (mnemonic != nullptr)
5591             strm.Printf("%s", mnemonic);
5592 
5593           int right_pad = 1;
5594           window.PutCStringTruncated(
5595               right_pad,
5596               strm.GetString().substr(m_first_visible_column).data());
5597 
5598           if (is_pc_line && frame_sp &&
5599               frame_sp->GetConcreteFrameIndex() == 0) {
5600             StopInfoSP stop_info_sp;
5601             if (thread)
5602               stop_info_sp = thread->GetStopInfo();
5603             if (stop_info_sp) {
5604               const char *stop_description = stop_info_sp->GetDescription();
5605               if (stop_description && stop_description[0]) {
5606                 size_t stop_description_len = strlen(stop_description);
5607                 int desc_x = window_width - stop_description_len - 16;
5608                 if (desc_x - window.GetCursorX() > 0)
5609                   window.Printf("%*s", desc_x - window.GetCursorX(), "");
5610                 window.MoveCursor(window_width - stop_description_len - 15,
5611                                   line_y);
5612                 window.PrintfTruncated(1, "<<< Thread %u: %s ",
5613                                        thread->GetIndexID(), stop_description);
5614               }
5615             } else {
5616               window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
5617             }
5618           }
5619           if (highlight_attr)
5620             window.AttributeOff(highlight_attr);
5621         }
5622       }
5623     }
5624     return true; // Drawing handled
5625   }
5626 
5627   size_t GetNumLines() {
5628     size_t num_lines = GetNumSourceLines();
5629     if (num_lines == 0)
5630       num_lines = GetNumDisassemblyLines();
5631     return num_lines;
5632   }
5633 
5634   size_t GetNumSourceLines() const {
5635     if (m_file_sp)
5636       return m_file_sp->GetNumLines();
5637     return 0;
5638   }
5639 
5640   size_t GetNumDisassemblyLines() const {
5641     if (m_disassembly_sp)
5642       return m_disassembly_sp->GetInstructionList().GetSize();
5643     return 0;
5644   }
5645 
5646   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
5647     const uint32_t num_visible_lines = NumVisibleLines();
5648     const size_t num_lines = GetNumLines();
5649 
5650     switch (c) {
5651     case ',':
5652     case KEY_PPAGE:
5653       // Page up key
5654       if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines)
5655         m_first_visible_line -= num_visible_lines;
5656       else
5657         m_first_visible_line = 0;
5658       m_selected_line = m_first_visible_line;
5659       return eKeyHandled;
5660 
5661     case '.':
5662     case KEY_NPAGE:
5663       // Page down key
5664       {
5665         if (m_first_visible_line + num_visible_lines < num_lines)
5666           m_first_visible_line += num_visible_lines;
5667         else if (num_lines < num_visible_lines)
5668           m_first_visible_line = 0;
5669         else
5670           m_first_visible_line = num_lines - num_visible_lines;
5671         m_selected_line = m_first_visible_line;
5672       }
5673       return eKeyHandled;
5674 
5675     case KEY_UP:
5676       if (m_selected_line > 0) {
5677         m_selected_line--;
5678         if (static_cast<size_t>(m_first_visible_line) > m_selected_line)
5679           m_first_visible_line = m_selected_line;
5680       }
5681       return eKeyHandled;
5682 
5683     case KEY_DOWN:
5684       if (m_selected_line + 1 < num_lines) {
5685         m_selected_line++;
5686         if (m_first_visible_line + num_visible_lines < m_selected_line)
5687           m_first_visible_line++;
5688       }
5689       return eKeyHandled;
5690 
5691     case KEY_LEFT:
5692       if (m_first_visible_column > 0)
5693         --m_first_visible_column;
5694       return eKeyHandled;
5695 
5696     case KEY_RIGHT:
5697       ++m_first_visible_column;
5698       return eKeyHandled;
5699 
5700     case '\r':
5701     case '\n':
5702     case KEY_ENTER:
5703       // Set a breakpoint and run to the line using a one shot breakpoint
5704       if (GetNumSourceLines() > 0) {
5705         ExecutionContext exe_ctx =
5706             m_debugger.GetCommandInterpreter().GetExecutionContext();
5707         if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) {
5708           BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
5709               nullptr, // Don't limit the breakpoint to certain modules
5710               m_file_sp->GetFileSpec(), // Source file
5711               m_selected_line +
5712                   1, // Source line number (m_selected_line is zero based)
5713               0,     // Unspecified column.
5714               0,     // No offset
5715               eLazyBoolCalculate,  // Check inlines using global setting
5716               eLazyBoolCalculate,  // Skip prologue using global setting,
5717               false,               // internal
5718               false,               // request_hardware
5719               eLazyBoolCalculate); // move_to_nearest_code
5720           // Make breakpoint one shot
5721           bp_sp->GetOptions().SetOneShot(true);
5722           exe_ctx.GetProcessRef().Resume();
5723         }
5724       } else if (m_selected_line < GetNumDisassemblyLines()) {
5725         const Instruction *inst = m_disassembly_sp->GetInstructionList()
5726                                       .GetInstructionAtIndex(m_selected_line)
5727                                       .get();
5728         ExecutionContext exe_ctx =
5729             m_debugger.GetCommandInterpreter().GetExecutionContext();
5730         if (exe_ctx.HasTargetScope()) {
5731           Address addr = inst->GetAddress();
5732           BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
5733               addr,   // lldb_private::Address
5734               false,  // internal
5735               false); // request_hardware
5736           // Make breakpoint one shot
5737           bp_sp->GetOptions().SetOneShot(true);
5738           exe_ctx.GetProcessRef().Resume();
5739         }
5740       }
5741       return eKeyHandled;
5742 
5743     case 'b': // 'b' == toggle breakpoint on currently selected line
5744       ToggleBreakpointOnSelectedLine();
5745       return eKeyHandled;
5746 
5747     case 'D': // 'D' == detach and keep stopped
5748     {
5749       ExecutionContext exe_ctx =
5750           m_debugger.GetCommandInterpreter().GetExecutionContext();
5751       if (exe_ctx.HasProcessScope())
5752         exe_ctx.GetProcessRef().Detach(true);
5753     }
5754       return eKeyHandled;
5755 
5756     case 'c':
5757       // 'c' == continue
5758       {
5759         ExecutionContext exe_ctx =
5760             m_debugger.GetCommandInterpreter().GetExecutionContext();
5761         if (exe_ctx.HasProcessScope())
5762           exe_ctx.GetProcessRef().Resume();
5763       }
5764       return eKeyHandled;
5765 
5766     case 'f':
5767       // 'f' == step out (finish)
5768       {
5769         ExecutionContext exe_ctx =
5770             m_debugger.GetCommandInterpreter().GetExecutionContext();
5771         if (exe_ctx.HasThreadScope() &&
5772             StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
5773           exe_ctx.GetThreadRef().StepOut();
5774         }
5775       }
5776       return eKeyHandled;
5777 
5778     case 'n': // 'n' == step over
5779     case 'N': // 'N' == step over instruction
5780     {
5781       ExecutionContext exe_ctx =
5782           m_debugger.GetCommandInterpreter().GetExecutionContext();
5783       if (exe_ctx.HasThreadScope() &&
5784           StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
5785         bool source_step = (c == 'n');
5786         exe_ctx.GetThreadRef().StepOver(source_step);
5787       }
5788     }
5789       return eKeyHandled;
5790 
5791     case 's': // 's' == step into
5792     case 'S': // 'S' == step into instruction
5793     {
5794       ExecutionContext exe_ctx =
5795           m_debugger.GetCommandInterpreter().GetExecutionContext();
5796       if (exe_ctx.HasThreadScope() &&
5797           StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
5798         bool source_step = (c == 's');
5799         exe_ctx.GetThreadRef().StepIn(source_step);
5800       }
5801     }
5802       return eKeyHandled;
5803 
5804     case 'u': // 'u' == frame up
5805     case 'd': // 'd' == frame down
5806     {
5807       ExecutionContext exe_ctx =
5808           m_debugger.GetCommandInterpreter().GetExecutionContext();
5809       if (exe_ctx.HasThreadScope()) {
5810         Thread *thread = exe_ctx.GetThreadPtr();
5811         uint32_t frame_idx = thread->GetSelectedFrameIndex();
5812         if (frame_idx == UINT32_MAX)
5813           frame_idx = 0;
5814         if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount())
5815           ++frame_idx;
5816         else if (c == 'd' && frame_idx > 0)
5817           --frame_idx;
5818         if (thread->SetSelectedFrameByIndex(frame_idx, true))
5819           exe_ctx.SetFrameSP(thread->GetSelectedFrame());
5820       }
5821     }
5822       return eKeyHandled;
5823 
5824     case 'h':
5825       window.CreateHelpSubwindow();
5826       return eKeyHandled;
5827 
5828     default:
5829       break;
5830     }
5831     return eKeyNotHandled;
5832   }
5833 
5834   void ToggleBreakpointOnSelectedLine() {
5835     ExecutionContext exe_ctx =
5836         m_debugger.GetCommandInterpreter().GetExecutionContext();
5837     if (!exe_ctx.HasTargetScope())
5838       return;
5839     if (GetNumSourceLines() > 0) {
5840       // Source file breakpoint.
5841       BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
5842       const size_t num_bps = bp_list.GetSize();
5843       for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
5844         BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
5845         const size_t num_bps_locs = bp_sp->GetNumLocations();
5846         for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
5847           BreakpointLocationSP bp_loc_sp =
5848               bp_sp->GetLocationAtIndex(bp_loc_idx);
5849           LineEntry bp_loc_line_entry;
5850           if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
5851                   bp_loc_line_entry)) {
5852             if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file &&
5853                 m_selected_line + 1 == bp_loc_line_entry.line) {
5854               bool removed =
5855                   exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
5856               assert(removed);
5857               UNUSED_IF_ASSERT_DISABLED(removed);
5858               return; // Existing breakpoint removed.
5859             }
5860           }
5861         }
5862       }
5863       // No breakpoint found on the location, add it.
5864       BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
5865           nullptr, // Don't limit the breakpoint to certain modules
5866           m_file_sp->GetFileSpec(), // Source file
5867           m_selected_line +
5868               1, // Source line number (m_selected_line is zero based)
5869           0,     // No column specified.
5870           0,     // No offset
5871           eLazyBoolCalculate,  // Check inlines using global setting
5872           eLazyBoolCalculate,  // Skip prologue using global setting,
5873           false,               // internal
5874           false,               // request_hardware
5875           eLazyBoolCalculate); // move_to_nearest_code
5876     } else {
5877       // Disassembly breakpoint.
5878       assert(GetNumDisassemblyLines() > 0);
5879       assert(m_selected_line < GetNumDisassemblyLines());
5880       const Instruction *inst = m_disassembly_sp->GetInstructionList()
5881                                     .GetInstructionAtIndex(m_selected_line)
5882                                     .get();
5883       Address addr = inst->GetAddress();
5884       // Try to find it.
5885       BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
5886       const size_t num_bps = bp_list.GetSize();
5887       for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
5888         BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
5889         const size_t num_bps_locs = bp_sp->GetNumLocations();
5890         for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
5891           BreakpointLocationSP bp_loc_sp =
5892               bp_sp->GetLocationAtIndex(bp_loc_idx);
5893           LineEntry bp_loc_line_entry;
5894           const lldb::addr_t file_addr =
5895               bp_loc_sp->GetAddress().GetFileAddress();
5896           if (file_addr == addr.GetFileAddress()) {
5897             bool removed =
5898                 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
5899             assert(removed);
5900             UNUSED_IF_ASSERT_DISABLED(removed);
5901             return; // Existing breakpoint removed.
5902           }
5903         }
5904       }
5905       // No breakpoint found on the address, add it.
5906       BreakpointSP bp_sp =
5907           exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address
5908                                                   false,  // internal
5909                                                   false); // request_hardware
5910     }
5911   }
5912 
5913 protected:
5914   typedef std::set<uint32_t> BreakpointLines;
5915   typedef std::set<lldb::addr_t> BreakpointAddrs;
5916 
5917   Debugger &m_debugger;
5918   SymbolContext m_sc;
5919   SourceManager::FileSP m_file_sp;
5920   SymbolContextScope *m_disassembly_scope;
5921   lldb::DisassemblerSP m_disassembly_sp;
5922   AddressRange m_disassembly_range;
5923   StreamString m_title;
5924   lldb::user_id_t m_tid;
5925   int m_line_width;
5926   uint32_t m_selected_line; // The selected line
5927   uint32_t m_pc_line;       // The line with the PC
5928   uint32_t m_stop_id;
5929   uint32_t m_frame_idx;
5930   int m_first_visible_line;
5931   int m_first_visible_column;
5932   int m_min_x;
5933   int m_min_y;
5934   int m_max_x;
5935   int m_max_y;
5936 };
5937 
5938 DisplayOptions ValueObjectListDelegate::g_options = {true};
5939 
5940 IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger)
5941     : IOHandler(debugger, IOHandler::Type::Curses) {}
5942 
5943 void IOHandlerCursesGUI::Activate() {
5944   IOHandler::Activate();
5945   if (!m_app_ap) {
5946     m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE());
5947 
5948     // This is both a window and a menu delegate
5949     std::shared_ptr<ApplicationDelegate> app_delegate_sp(
5950         new ApplicationDelegate(*m_app_ap, m_debugger));
5951 
5952     MenuDelegateSP app_menu_delegate_sp =
5953         std::static_pointer_cast<MenuDelegate>(app_delegate_sp);
5954     MenuSP lldb_menu_sp(
5955         new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB));
5956     MenuSP exit_menuitem_sp(
5957         new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit));
5958     exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit);
5959     lldb_menu_sp->AddSubmenu(MenuSP(new Menu(
5960         "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout)));
5961     lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
5962     lldb_menu_sp->AddSubmenu(exit_menuitem_sp);
5963 
5964     MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2),
5965                                    ApplicationDelegate::eMenuID_Target));
5966     target_menu_sp->AddSubmenu(MenuSP(new Menu(
5967         "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate)));
5968     target_menu_sp->AddSubmenu(MenuSP(new Menu(
5969         "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete)));
5970 
5971     MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3),
5972                                     ApplicationDelegate::eMenuID_Process));
5973     process_menu_sp->AddSubmenu(MenuSP(new Menu(
5974         "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach)));
5975     process_menu_sp->AddSubmenu(
5976         MenuSP(new Menu("Detach and resume", nullptr, 'd',
5977                         ApplicationDelegate::eMenuID_ProcessDetachResume)));
5978     process_menu_sp->AddSubmenu(
5979         MenuSP(new Menu("Detach suspended", nullptr, 's',
5980                         ApplicationDelegate::eMenuID_ProcessDetachSuspended)));
5981     process_menu_sp->AddSubmenu(MenuSP(new Menu(
5982         "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch)));
5983     process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
5984     process_menu_sp->AddSubmenu(
5985         MenuSP(new Menu("Continue", nullptr, 'c',
5986                         ApplicationDelegate::eMenuID_ProcessContinue)));
5987     process_menu_sp->AddSubmenu(MenuSP(new Menu(
5988         "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt)));
5989     process_menu_sp->AddSubmenu(MenuSP(new Menu(
5990         "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill)));
5991 
5992     MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4),
5993                                    ApplicationDelegate::eMenuID_Thread));
5994     thread_menu_sp->AddSubmenu(MenuSP(new Menu(
5995         "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn)));
5996     thread_menu_sp->AddSubmenu(
5997         MenuSP(new Menu("Step Over", nullptr, 'v',
5998                         ApplicationDelegate::eMenuID_ThreadStepOver)));
5999     thread_menu_sp->AddSubmenu(MenuSP(new Menu(
6000         "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut)));
6001 
6002     MenuSP view_menu_sp(
6003         new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View));
6004     view_menu_sp->AddSubmenu(
6005         MenuSP(new Menu("Backtrace", nullptr, 'b',
6006                         ApplicationDelegate::eMenuID_ViewBacktrace)));
6007     view_menu_sp->AddSubmenu(
6008         MenuSP(new Menu("Registers", nullptr, 'r',
6009                         ApplicationDelegate::eMenuID_ViewRegisters)));
6010     view_menu_sp->AddSubmenu(MenuSP(new Menu(
6011         "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource)));
6012     view_menu_sp->AddSubmenu(
6013         MenuSP(new Menu("Variables", nullptr, 'v',
6014                         ApplicationDelegate::eMenuID_ViewVariables)));
6015 
6016     MenuSP help_menu_sp(
6017         new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help));
6018     help_menu_sp->AddSubmenu(MenuSP(new Menu(
6019         "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp)));
6020 
6021     m_app_ap->Initialize();
6022     WindowSP &main_window_sp = m_app_ap->GetMainWindow();
6023 
6024     MenuSP menubar_sp(new Menu(Menu::Type::Bar));
6025     menubar_sp->AddSubmenu(lldb_menu_sp);
6026     menubar_sp->AddSubmenu(target_menu_sp);
6027     menubar_sp->AddSubmenu(process_menu_sp);
6028     menubar_sp->AddSubmenu(thread_menu_sp);
6029     menubar_sp->AddSubmenu(view_menu_sp);
6030     menubar_sp->AddSubmenu(help_menu_sp);
6031     menubar_sp->SetDelegate(app_menu_delegate_sp);
6032 
6033     Rect content_bounds = main_window_sp->GetFrame();
6034     Rect menubar_bounds = content_bounds.MakeMenuBar();
6035     Rect status_bounds = content_bounds.MakeStatusBar();
6036     Rect source_bounds;
6037     Rect variables_bounds;
6038     Rect threads_bounds;
6039     Rect source_variables_bounds;
6040     content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
6041                                            threads_bounds);
6042     source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds,
6043                                                       variables_bounds);
6044 
6045     WindowSP menubar_window_sp =
6046         main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false);
6047     // Let the menubar get keys if the active window doesn't handle the keys
6048     // that are typed so it can respond to menubar key presses.
6049     menubar_window_sp->SetCanBeActive(
6050         false); // Don't let the menubar become the active window
6051     menubar_window_sp->SetDelegate(menubar_sp);
6052 
6053     WindowSP source_window_sp(
6054         main_window_sp->CreateSubWindow("Source", source_bounds, true));
6055     WindowSP variables_window_sp(
6056         main_window_sp->CreateSubWindow("Variables", variables_bounds, false));
6057     WindowSP threads_window_sp(
6058         main_window_sp->CreateSubWindow("Threads", threads_bounds, false));
6059     WindowSP status_window_sp(
6060         main_window_sp->CreateSubWindow("Status", status_bounds, false));
6061     status_window_sp->SetCanBeActive(
6062         false); // Don't let the status bar become the active window
6063     main_window_sp->SetDelegate(
6064         std::static_pointer_cast<WindowDelegate>(app_delegate_sp));
6065     source_window_sp->SetDelegate(
6066         WindowDelegateSP(new SourceFileWindowDelegate(m_debugger)));
6067     variables_window_sp->SetDelegate(
6068         WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
6069     TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger));
6070     threads_window_sp->SetDelegate(WindowDelegateSP(
6071         new TreeWindowDelegate(m_debugger, thread_delegate_sp)));
6072     status_window_sp->SetDelegate(
6073         WindowDelegateSP(new StatusBarWindowDelegate(m_debugger)));
6074 
6075     // Show the main help window once the first time the curses GUI is launched
6076     static bool g_showed_help = false;
6077     if (!g_showed_help) {
6078       g_showed_help = true;
6079       main_window_sp->CreateHelpSubwindow();
6080     }
6081 
6082     // All colors with black background.
6083     init_pair(1, COLOR_BLACK, COLOR_BLACK);
6084     init_pair(2, COLOR_RED, COLOR_BLACK);
6085     init_pair(3, COLOR_GREEN, COLOR_BLACK);
6086     init_pair(4, COLOR_YELLOW, COLOR_BLACK);
6087     init_pair(5, COLOR_BLUE, COLOR_BLACK);
6088     init_pair(6, COLOR_MAGENTA, COLOR_BLACK);
6089     init_pair(7, COLOR_CYAN, COLOR_BLACK);
6090     init_pair(8, COLOR_WHITE, COLOR_BLACK);
6091     // All colors with blue background.
6092     init_pair(9, COLOR_BLACK, COLOR_BLUE);
6093     init_pair(10, COLOR_RED, COLOR_BLUE);
6094     init_pair(11, COLOR_GREEN, COLOR_BLUE);
6095     init_pair(12, COLOR_YELLOW, COLOR_BLUE);
6096     init_pair(13, COLOR_BLUE, COLOR_BLUE);
6097     init_pair(14, COLOR_MAGENTA, COLOR_BLUE);
6098     init_pair(15, COLOR_CYAN, COLOR_BLUE);
6099     init_pair(16, COLOR_WHITE, COLOR_BLUE);
6100     // These must match the order in the color indexes enum.
6101     init_pair(17, COLOR_BLACK, COLOR_WHITE);
6102     init_pair(18, COLOR_MAGENTA, COLOR_WHITE);
6103     static_assert(LastColorPairIndex == 18, "Color indexes do not match.");
6104 
6105     define_key("\033[Z", KEY_SHIFT_TAB);
6106   }
6107 }
6108 
6109 void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); }
6110 
6111 void IOHandlerCursesGUI::Run() {
6112   m_app_ap->Run(m_debugger);
6113   SetIsDone(true);
6114 }
6115 
6116 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default;
6117 
6118 void IOHandlerCursesGUI::Cancel() {}
6119 
6120 bool IOHandlerCursesGUI::Interrupt() { return false; }
6121 
6122 void IOHandlerCursesGUI::GotEOF() {}
6123 
6124 void IOHandlerCursesGUI::TerminalSizeChanged() {
6125   m_app_ap->TerminalSizeChanged();
6126 }
6127 
6128 #endif // LLDB_ENABLE_CURSES
6129