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