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