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