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