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