xref: /openbsd-src/gnu/llvm/lldb/source/Core/IOHandlerCursesGUI.cpp (revision f6aab3d83b51b91c24247ad2c2573574de475a82)
1dda28197Spatrick //===-- IOHandlerCursesGUI.cpp --------------------------------------------===//
2061da546Spatrick //
3061da546Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4061da546Spatrick // See https://llvm.org/LICENSE.txt for license information.
5061da546Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6061da546Spatrick //
7061da546Spatrick //===----------------------------------------------------------------------===//
8061da546Spatrick 
9061da546Spatrick #include "lldb/Core/IOHandlerCursesGUI.h"
10061da546Spatrick #include "lldb/Host/Config.h"
11061da546Spatrick 
12061da546Spatrick #if LLDB_ENABLE_CURSES
13be691f3bSpatrick #if CURSES_HAVE_NCURSES_CURSES_H
14be691f3bSpatrick #include <ncurses/curses.h>
15be691f3bSpatrick #include <ncurses/panel.h>
16be691f3bSpatrick #else
17061da546Spatrick #include <curses.h>
18061da546Spatrick #include <panel.h>
19061da546Spatrick #endif
20be691f3bSpatrick #endif
21061da546Spatrick 
22061da546Spatrick #if defined(__APPLE__)
23061da546Spatrick #include <deque>
24061da546Spatrick #endif
25061da546Spatrick #include <string>
26061da546Spatrick 
27061da546Spatrick #include "lldb/Core/Debugger.h"
28061da546Spatrick #include "lldb/Core/StreamFile.h"
29be691f3bSpatrick #include "lldb/Core/ValueObjectUpdater.h"
30061da546Spatrick #include "lldb/Host/File.h"
31*f6aab3d8Srobert #include "lldb/Utility/AnsiTerminal.h"
32061da546Spatrick #include "lldb/Utility/Predicate.h"
33061da546Spatrick #include "lldb/Utility/Status.h"
34061da546Spatrick #include "lldb/Utility/StreamString.h"
35061da546Spatrick #include "lldb/Utility/StringList.h"
36061da546Spatrick #include "lldb/lldb-forward.h"
37061da546Spatrick 
38061da546Spatrick #include "lldb/Interpreter/CommandCompletions.h"
39061da546Spatrick #include "lldb/Interpreter/CommandInterpreter.h"
40*f6aab3d8Srobert #include "lldb/Interpreter/OptionGroupPlatform.h"
41061da546Spatrick 
42061da546Spatrick #if LLDB_ENABLE_CURSES
43061da546Spatrick #include "lldb/Breakpoint/BreakpointLocation.h"
44061da546Spatrick #include "lldb/Core/Module.h"
45be691f3bSpatrick #include "lldb/Core/PluginManager.h"
46061da546Spatrick #include "lldb/Core/ValueObject.h"
47061da546Spatrick #include "lldb/Core/ValueObjectRegister.h"
48061da546Spatrick #include "lldb/Symbol/Block.h"
49*f6aab3d8Srobert #include "lldb/Symbol/CompileUnit.h"
50061da546Spatrick #include "lldb/Symbol/Function.h"
51061da546Spatrick #include "lldb/Symbol/Symbol.h"
52061da546Spatrick #include "lldb/Symbol/VariableList.h"
53061da546Spatrick #include "lldb/Target/Process.h"
54061da546Spatrick #include "lldb/Target/RegisterContext.h"
55061da546Spatrick #include "lldb/Target/StackFrame.h"
56061da546Spatrick #include "lldb/Target/StopInfo.h"
57061da546Spatrick #include "lldb/Target/Target.h"
58061da546Spatrick #include "lldb/Target/Thread.h"
59061da546Spatrick #include "lldb/Utility/State.h"
60061da546Spatrick #endif
61061da546Spatrick 
62061da546Spatrick #include "llvm/ADT/StringRef.h"
63061da546Spatrick 
64061da546Spatrick #ifdef _WIN32
65061da546Spatrick #include "lldb/Host/windows/windows.h"
66061da546Spatrick #endif
67061da546Spatrick 
68061da546Spatrick #include <memory>
69061da546Spatrick #include <mutex>
70061da546Spatrick 
71be691f3bSpatrick #include <cassert>
72be691f3bSpatrick #include <cctype>
73be691f3bSpatrick #include <cerrno>
74be691f3bSpatrick #include <cstdint>
75be691f3bSpatrick #include <cstdio>
76be691f3bSpatrick #include <cstring>
77be691f3bSpatrick #include <functional>
78*f6aab3d8Srobert #include <optional>
79061da546Spatrick #include <type_traits>
80061da546Spatrick 
81061da546Spatrick using namespace lldb;
82061da546Spatrick using namespace lldb_private;
83061da546Spatrick using llvm::StringRef;
84061da546Spatrick 
85061da546Spatrick // we may want curses to be disabled for some builds for instance, windows
86061da546Spatrick #if LLDB_ENABLE_CURSES
87061da546Spatrick 
88*f6aab3d8Srobert #define KEY_CTRL_A 1
89*f6aab3d8Srobert #define KEY_CTRL_E 5
90*f6aab3d8Srobert #define KEY_CTRL_K 11
91061da546Spatrick #define KEY_RETURN 10
92061da546Spatrick #define KEY_ESCAPE 27
93*f6aab3d8Srobert #define KEY_DELETE 127
94061da546Spatrick 
95be691f3bSpatrick #define KEY_SHIFT_TAB (KEY_MAX + 1)
96*f6aab3d8Srobert #define KEY_ALT_ENTER (KEY_MAX + 2)
97be691f3bSpatrick 
98061da546Spatrick namespace curses {
99061da546Spatrick class Menu;
100061da546Spatrick class MenuDelegate;
101061da546Spatrick class Window;
102061da546Spatrick class WindowDelegate;
103061da546Spatrick typedef std::shared_ptr<Menu> MenuSP;
104061da546Spatrick typedef std::shared_ptr<MenuDelegate> MenuDelegateSP;
105061da546Spatrick typedef std::shared_ptr<Window> WindowSP;
106061da546Spatrick typedef std::shared_ptr<WindowDelegate> WindowDelegateSP;
107061da546Spatrick typedef std::vector<MenuSP> Menus;
108061da546Spatrick typedef std::vector<WindowSP> Windows;
109061da546Spatrick typedef std::vector<WindowDelegateSP> WindowDelegates;
110061da546Spatrick 
111061da546Spatrick #if 0
112061da546Spatrick type summary add -s "x=${var.x}, y=${var.y}" curses::Point
113061da546Spatrick type summary add -s "w=${var.width}, h=${var.height}" curses::Size
114061da546Spatrick type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect
115061da546Spatrick #endif
116061da546Spatrick 
117061da546Spatrick struct Point {
118061da546Spatrick   int x;
119061da546Spatrick   int y;
120061da546Spatrick 
Pointcurses::Point121061da546Spatrick   Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}
122061da546Spatrick 
Clearcurses::Point123061da546Spatrick   void Clear() {
124061da546Spatrick     x = 0;
125061da546Spatrick     y = 0;
126061da546Spatrick   }
127061da546Spatrick 
operator +=curses::Point128061da546Spatrick   Point &operator+=(const Point &rhs) {
129061da546Spatrick     x += rhs.x;
130061da546Spatrick     y += rhs.y;
131061da546Spatrick     return *this;
132061da546Spatrick   }
133061da546Spatrick 
Dumpcurses::Point134061da546Spatrick   void Dump() { printf("(x=%i, y=%i)\n", x, y); }
135061da546Spatrick };
136061da546Spatrick 
operator ==(const Point & lhs,const Point & rhs)137061da546Spatrick bool operator==(const Point &lhs, const Point &rhs) {
138061da546Spatrick   return lhs.x == rhs.x && lhs.y == rhs.y;
139061da546Spatrick }
140061da546Spatrick 
operator !=(const Point & lhs,const Point & rhs)141061da546Spatrick bool operator!=(const Point &lhs, const Point &rhs) {
142061da546Spatrick   return lhs.x != rhs.x || lhs.y != rhs.y;
143061da546Spatrick }
144061da546Spatrick 
145061da546Spatrick struct Size {
146061da546Spatrick   int width;
147061da546Spatrick   int height;
Sizecurses::Size148061da546Spatrick   Size(int w = 0, int h = 0) : width(w), height(h) {}
149061da546Spatrick 
Clearcurses::Size150061da546Spatrick   void Clear() {
151061da546Spatrick     width = 0;
152061da546Spatrick     height = 0;
153061da546Spatrick   }
154061da546Spatrick 
Dumpcurses::Size155061da546Spatrick   void Dump() { printf("(w=%i, h=%i)\n", width, height); }
156061da546Spatrick };
157061da546Spatrick 
operator ==(const Size & lhs,const Size & rhs)158061da546Spatrick bool operator==(const Size &lhs, const Size &rhs) {
159061da546Spatrick   return lhs.width == rhs.width && lhs.height == rhs.height;
160061da546Spatrick }
161061da546Spatrick 
operator !=(const Size & lhs,const Size & rhs)162061da546Spatrick bool operator!=(const Size &lhs, const Size &rhs) {
163061da546Spatrick   return lhs.width != rhs.width || lhs.height != rhs.height;
164061da546Spatrick }
165061da546Spatrick 
166061da546Spatrick struct Rect {
167061da546Spatrick   Point origin;
168061da546Spatrick   Size size;
169061da546Spatrick 
Rectcurses::Rect170061da546Spatrick   Rect() : origin(), size() {}
171061da546Spatrick 
Rectcurses::Rect172061da546Spatrick   Rect(const Point &p, const Size &s) : origin(p), size(s) {}
173061da546Spatrick 
Clearcurses::Rect174061da546Spatrick   void Clear() {
175061da546Spatrick     origin.Clear();
176061da546Spatrick     size.Clear();
177061da546Spatrick   }
178061da546Spatrick 
Dumpcurses::Rect179061da546Spatrick   void Dump() {
180061da546Spatrick     printf("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width,
181061da546Spatrick            size.height);
182061da546Spatrick   }
183061da546Spatrick 
Insetcurses::Rect184061da546Spatrick   void Inset(int w, int h) {
185061da546Spatrick     if (size.width > w * 2)
186061da546Spatrick       size.width -= w * 2;
187061da546Spatrick     origin.x += w;
188061da546Spatrick 
189061da546Spatrick     if (size.height > h * 2)
190061da546Spatrick       size.height -= h * 2;
191061da546Spatrick     origin.y += h;
192061da546Spatrick   }
193061da546Spatrick 
194061da546Spatrick   // Return a status bar rectangle which is the last line of this rectangle.
195061da546Spatrick   // This rectangle will be modified to not include the status bar area.
MakeStatusBarcurses::Rect196061da546Spatrick   Rect MakeStatusBar() {
197061da546Spatrick     Rect status_bar;
198061da546Spatrick     if (size.height > 1) {
199061da546Spatrick       status_bar.origin.x = origin.x;
200061da546Spatrick       status_bar.origin.y = size.height;
201061da546Spatrick       status_bar.size.width = size.width;
202061da546Spatrick       status_bar.size.height = 1;
203061da546Spatrick       --size.height;
204061da546Spatrick     }
205061da546Spatrick     return status_bar;
206061da546Spatrick   }
207061da546Spatrick 
208061da546Spatrick   // Return a menubar rectangle which is the first line of this rectangle. This
209061da546Spatrick   // rectangle will be modified to not include the menubar area.
MakeMenuBarcurses::Rect210061da546Spatrick   Rect MakeMenuBar() {
211061da546Spatrick     Rect menubar;
212061da546Spatrick     if (size.height > 1) {
213061da546Spatrick       menubar.origin.x = origin.x;
214061da546Spatrick       menubar.origin.y = origin.y;
215061da546Spatrick       menubar.size.width = size.width;
216061da546Spatrick       menubar.size.height = 1;
217061da546Spatrick       ++origin.y;
218061da546Spatrick       --size.height;
219061da546Spatrick     }
220061da546Spatrick     return menubar;
221061da546Spatrick   }
222061da546Spatrick 
HorizontalSplitPercentagecurses::Rect223061da546Spatrick   void HorizontalSplitPercentage(float top_percentage, Rect &top,
224061da546Spatrick                                  Rect &bottom) const {
225061da546Spatrick     float top_height = top_percentage * size.height;
226061da546Spatrick     HorizontalSplit(top_height, top, bottom);
227061da546Spatrick   }
228061da546Spatrick 
HorizontalSplitcurses::Rect229061da546Spatrick   void HorizontalSplit(int top_height, Rect &top, Rect &bottom) const {
230061da546Spatrick     top = *this;
231061da546Spatrick     if (top_height < size.height) {
232061da546Spatrick       top.size.height = top_height;
233061da546Spatrick       bottom.origin.x = origin.x;
234061da546Spatrick       bottom.origin.y = origin.y + top.size.height;
235061da546Spatrick       bottom.size.width = size.width;
236061da546Spatrick       bottom.size.height = size.height - top.size.height;
237061da546Spatrick     } else {
238061da546Spatrick       bottom.Clear();
239061da546Spatrick     }
240061da546Spatrick   }
241061da546Spatrick 
VerticalSplitPercentagecurses::Rect242061da546Spatrick   void VerticalSplitPercentage(float left_percentage, Rect &left,
243061da546Spatrick                                Rect &right) const {
244061da546Spatrick     float left_width = left_percentage * size.width;
245061da546Spatrick     VerticalSplit(left_width, left, right);
246061da546Spatrick   }
247061da546Spatrick 
VerticalSplitcurses::Rect248061da546Spatrick   void VerticalSplit(int left_width, Rect &left, Rect &right) const {
249061da546Spatrick     left = *this;
250061da546Spatrick     if (left_width < size.width) {
251061da546Spatrick       left.size.width = left_width;
252061da546Spatrick       right.origin.x = origin.x + left.size.width;
253061da546Spatrick       right.origin.y = origin.y;
254061da546Spatrick       right.size.width = size.width - left.size.width;
255061da546Spatrick       right.size.height = size.height;
256061da546Spatrick     } else {
257061da546Spatrick       right.Clear();
258061da546Spatrick     }
259061da546Spatrick   }
260061da546Spatrick };
261061da546Spatrick 
operator ==(const Rect & lhs,const Rect & rhs)262061da546Spatrick bool operator==(const Rect &lhs, const Rect &rhs) {
263061da546Spatrick   return lhs.origin == rhs.origin && lhs.size == rhs.size;
264061da546Spatrick }
265061da546Spatrick 
operator !=(const Rect & lhs,const Rect & rhs)266061da546Spatrick bool operator!=(const Rect &lhs, const Rect &rhs) {
267061da546Spatrick   return lhs.origin != rhs.origin || lhs.size != rhs.size;
268061da546Spatrick }
269061da546Spatrick 
270061da546Spatrick enum HandleCharResult {
271061da546Spatrick   eKeyNotHandled = 0,
272061da546Spatrick   eKeyHandled = 1,
273061da546Spatrick   eQuitApplication = 2
274061da546Spatrick };
275061da546Spatrick 
276061da546Spatrick enum class MenuActionResult {
277061da546Spatrick   Handled,
278061da546Spatrick   NotHandled,
279061da546Spatrick   Quit // Exit all menus and quit
280061da546Spatrick };
281061da546Spatrick 
282061da546Spatrick struct KeyHelp {
283061da546Spatrick   int ch;
284061da546Spatrick   const char *description;
285061da546Spatrick };
286061da546Spatrick 
287be691f3bSpatrick // COLOR_PAIR index names
288be691f3bSpatrick enum {
289be691f3bSpatrick   // First 16 colors are 8 black background and 8 blue background colors,
290be691f3bSpatrick   // needed by OutputColoredStringTruncated().
291be691f3bSpatrick   BlackOnBlack = 1,
292be691f3bSpatrick   RedOnBlack,
293be691f3bSpatrick   GreenOnBlack,
294be691f3bSpatrick   YellowOnBlack,
295be691f3bSpatrick   BlueOnBlack,
296be691f3bSpatrick   MagentaOnBlack,
297be691f3bSpatrick   CyanOnBlack,
298be691f3bSpatrick   WhiteOnBlack,
299be691f3bSpatrick   BlackOnBlue,
300be691f3bSpatrick   RedOnBlue,
301be691f3bSpatrick   GreenOnBlue,
302be691f3bSpatrick   YellowOnBlue,
303be691f3bSpatrick   BlueOnBlue,
304be691f3bSpatrick   MagentaOnBlue,
305be691f3bSpatrick   CyanOnBlue,
306be691f3bSpatrick   WhiteOnBlue,
307be691f3bSpatrick   // Other colors, as needed.
308be691f3bSpatrick   BlackOnWhite,
309be691f3bSpatrick   MagentaOnWhite,
310be691f3bSpatrick   LastColorPairIndex = MagentaOnWhite
311be691f3bSpatrick };
312be691f3bSpatrick 
313061da546Spatrick class WindowDelegate {
314061da546Spatrick public:
315061da546Spatrick   virtual ~WindowDelegate() = default;
316061da546Spatrick 
WindowDelegateDraw(Window & window,bool force)317061da546Spatrick   virtual bool WindowDelegateDraw(Window &window, bool force) {
318061da546Spatrick     return false; // Drawing not handled
319061da546Spatrick   }
320061da546Spatrick 
WindowDelegateHandleChar(Window & window,int key)321061da546Spatrick   virtual HandleCharResult WindowDelegateHandleChar(Window &window, int key) {
322061da546Spatrick     return eKeyNotHandled;
323061da546Spatrick   }
324061da546Spatrick 
WindowDelegateGetHelpText()325061da546Spatrick   virtual const char *WindowDelegateGetHelpText() { return nullptr; }
326061da546Spatrick 
WindowDelegateGetKeyHelp()327061da546Spatrick   virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; }
328061da546Spatrick };
329061da546Spatrick 
330061da546Spatrick class HelpDialogDelegate : public WindowDelegate {
331061da546Spatrick public:
332061da546Spatrick   HelpDialogDelegate(const char *text, KeyHelp *key_help_array);
333061da546Spatrick 
334061da546Spatrick   ~HelpDialogDelegate() override;
335061da546Spatrick 
336061da546Spatrick   bool WindowDelegateDraw(Window &window, bool force) override;
337061da546Spatrick 
338061da546Spatrick   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
339061da546Spatrick 
GetNumLines() const340061da546Spatrick   size_t GetNumLines() const { return m_text.GetSize(); }
341061da546Spatrick 
GetMaxLineLength() const342061da546Spatrick   size_t GetMaxLineLength() const { return m_text.GetMaxStringLength(); }
343061da546Spatrick 
344061da546Spatrick protected:
345061da546Spatrick   StringList m_text;
346*f6aab3d8Srobert   int m_first_visible_line = 0;
347061da546Spatrick };
348061da546Spatrick 
349be691f3bSpatrick // A surface is an abstraction for something than can be drawn on. The surface
350be691f3bSpatrick // have a width, a height, a cursor position, and a multitude of drawing
351be691f3bSpatrick // operations. This type should be sub-classed to get an actually useful ncurses
352*f6aab3d8Srobert // object, such as a Window or a Pad.
353be691f3bSpatrick class Surface {
354be691f3bSpatrick public:
355*f6aab3d8Srobert   enum class Type { Window, Pad };
356*f6aab3d8Srobert 
Surface(Surface::Type type)357*f6aab3d8Srobert   Surface(Surface::Type type) : m_type(type) {}
358be691f3bSpatrick 
get()359be691f3bSpatrick   WINDOW *get() { return m_window; }
360be691f3bSpatrick 
operator WINDOW*()361be691f3bSpatrick   operator WINDOW *() { return m_window; }
362be691f3bSpatrick 
SubSurface(Rect bounds)363*f6aab3d8Srobert   Surface SubSurface(Rect bounds) {
364*f6aab3d8Srobert     Surface subSurface(m_type);
365*f6aab3d8Srobert     if (m_type == Type::Pad)
366*f6aab3d8Srobert       subSurface.m_window =
367*f6aab3d8Srobert           ::subpad(m_window, bounds.size.height, bounds.size.width,
368*f6aab3d8Srobert                    bounds.origin.y, bounds.origin.x);
369*f6aab3d8Srobert     else
370*f6aab3d8Srobert       subSurface.m_window =
371*f6aab3d8Srobert           ::derwin(m_window, bounds.size.height, bounds.size.width,
372*f6aab3d8Srobert                    bounds.origin.y, bounds.origin.x);
373*f6aab3d8Srobert     return subSurface;
374*f6aab3d8Srobert   }
375*f6aab3d8Srobert 
376be691f3bSpatrick   // Copy a region of the surface to another surface.
CopyToSurface(Surface & target,Point source_origin,Point target_origin,Size size)377be691f3bSpatrick   void CopyToSurface(Surface &target, Point source_origin, Point target_origin,
378be691f3bSpatrick                      Size size) {
379be691f3bSpatrick     ::copywin(m_window, target.get(), source_origin.y, source_origin.x,
380be691f3bSpatrick               target_origin.y, target_origin.x,
381be691f3bSpatrick               target_origin.y + size.height - 1,
382be691f3bSpatrick               target_origin.x + size.width - 1, false);
383be691f3bSpatrick   }
384be691f3bSpatrick 
GetCursorX() const385be691f3bSpatrick   int GetCursorX() const { return getcurx(m_window); }
GetCursorY() const386be691f3bSpatrick   int GetCursorY() const { return getcury(m_window); }
MoveCursor(int x,int y)387be691f3bSpatrick   void MoveCursor(int x, int y) { ::wmove(m_window, y, x); }
388be691f3bSpatrick 
AttributeOn(attr_t attr)389be691f3bSpatrick   void AttributeOn(attr_t attr) { ::wattron(m_window, attr); }
AttributeOff(attr_t attr)390be691f3bSpatrick   void AttributeOff(attr_t attr) { ::wattroff(m_window, attr); }
391be691f3bSpatrick 
GetMaxX() const392be691f3bSpatrick   int GetMaxX() const { return getmaxx(m_window); }
GetMaxY() const393be691f3bSpatrick   int GetMaxY() const { return getmaxy(m_window); }
GetWidth() const394be691f3bSpatrick   int GetWidth() const { return GetMaxX(); }
GetHeight() const395be691f3bSpatrick   int GetHeight() const { return GetMaxY(); }
GetSize() const396be691f3bSpatrick   Size GetSize() const { return Size(GetWidth(), GetHeight()); }
397be691f3bSpatrick   // Get a zero origin rectangle width the surface size.
GetFrame() const398be691f3bSpatrick   Rect GetFrame() const { return Rect(Point(), GetSize()); }
399be691f3bSpatrick 
Clear()400be691f3bSpatrick   void Clear() { ::wclear(m_window); }
Erase()401be691f3bSpatrick   void Erase() { ::werase(m_window); }
402be691f3bSpatrick 
SetBackground(int color_pair_idx)403be691f3bSpatrick   void SetBackground(int color_pair_idx) {
404be691f3bSpatrick     ::wbkgd(m_window, COLOR_PAIR(color_pair_idx));
405be691f3bSpatrick   }
406be691f3bSpatrick 
PutChar(int ch)407be691f3bSpatrick   void PutChar(int ch) { ::waddch(m_window, ch); }
PutCString(const char * s,int len=-1)408be691f3bSpatrick   void PutCString(const char *s, int len = -1) { ::waddnstr(m_window, s, len); }
409be691f3bSpatrick 
PutCStringTruncated(int right_pad,const char * s,int len=-1)410be691f3bSpatrick   void PutCStringTruncated(int right_pad, const char *s, int len = -1) {
411be691f3bSpatrick     int bytes_left = GetWidth() - GetCursorX();
412be691f3bSpatrick     if (bytes_left > right_pad) {
413be691f3bSpatrick       bytes_left -= right_pad;
414be691f3bSpatrick       ::waddnstr(m_window, s, len < 0 ? bytes_left : std::min(bytes_left, len));
415be691f3bSpatrick     }
416be691f3bSpatrick   }
417be691f3bSpatrick 
Printf(const char * format,...)418be691f3bSpatrick   void Printf(const char *format, ...) __attribute__((format(printf, 2, 3))) {
419be691f3bSpatrick     va_list args;
420be691f3bSpatrick     va_start(args, format);
421be691f3bSpatrick     vw_printw(m_window, format, args);
422be691f3bSpatrick     va_end(args);
423be691f3bSpatrick   }
424be691f3bSpatrick 
PrintfTruncated(int right_pad,const char * format,...)425be691f3bSpatrick   void PrintfTruncated(int right_pad, const char *format, ...)
426be691f3bSpatrick       __attribute__((format(printf, 3, 4))) {
427be691f3bSpatrick     va_list args;
428be691f3bSpatrick     va_start(args, format);
429be691f3bSpatrick     StreamString strm;
430be691f3bSpatrick     strm.PrintfVarArg(format, args);
431be691f3bSpatrick     va_end(args);
432be691f3bSpatrick     PutCStringTruncated(right_pad, strm.GetData());
433be691f3bSpatrick   }
434be691f3bSpatrick 
VerticalLine(int n,chtype v_char=ACS_VLINE)435be691f3bSpatrick   void VerticalLine(int n, chtype v_char = ACS_VLINE) {
436be691f3bSpatrick     ::wvline(m_window, v_char, n);
437be691f3bSpatrick   }
HorizontalLine(int n,chtype h_char=ACS_HLINE)438be691f3bSpatrick   void HorizontalLine(int n, chtype h_char = ACS_HLINE) {
439be691f3bSpatrick     ::whline(m_window, h_char, n);
440be691f3bSpatrick   }
Box(chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)441be691f3bSpatrick   void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
442be691f3bSpatrick     ::box(m_window, v_char, h_char);
443be691f3bSpatrick   }
444be691f3bSpatrick 
TitledBox(const char * title,chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)445be691f3bSpatrick   void TitledBox(const char *title, chtype v_char = ACS_VLINE,
446be691f3bSpatrick                  chtype h_char = ACS_HLINE) {
447be691f3bSpatrick     Box(v_char, h_char);
448be691f3bSpatrick     int title_offset = 2;
449be691f3bSpatrick     MoveCursor(title_offset, 0);
450be691f3bSpatrick     PutChar('[');
451be691f3bSpatrick     PutCString(title, GetWidth() - title_offset);
452be691f3bSpatrick     PutChar(']');
453be691f3bSpatrick   }
454be691f3bSpatrick 
Box(const Rect & bounds,chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)455be691f3bSpatrick   void Box(const Rect &bounds, chtype v_char = ACS_VLINE,
456be691f3bSpatrick            chtype h_char = ACS_HLINE) {
457be691f3bSpatrick     MoveCursor(bounds.origin.x, bounds.origin.y);
458be691f3bSpatrick     VerticalLine(bounds.size.height);
459be691f3bSpatrick     HorizontalLine(bounds.size.width);
460be691f3bSpatrick     PutChar(ACS_ULCORNER);
461be691f3bSpatrick 
462be691f3bSpatrick     MoveCursor(bounds.origin.x + bounds.size.width - 1, bounds.origin.y);
463be691f3bSpatrick     VerticalLine(bounds.size.height);
464be691f3bSpatrick     PutChar(ACS_URCORNER);
465be691f3bSpatrick 
466be691f3bSpatrick     MoveCursor(bounds.origin.x, bounds.origin.y + bounds.size.height - 1);
467be691f3bSpatrick     HorizontalLine(bounds.size.width);
468be691f3bSpatrick     PutChar(ACS_LLCORNER);
469be691f3bSpatrick 
470be691f3bSpatrick     MoveCursor(bounds.origin.x + bounds.size.width - 1,
471be691f3bSpatrick                bounds.origin.y + bounds.size.height - 1);
472be691f3bSpatrick     PutChar(ACS_LRCORNER);
473be691f3bSpatrick   }
474be691f3bSpatrick 
TitledBox(const Rect & bounds,const char * title,chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)475be691f3bSpatrick   void TitledBox(const Rect &bounds, const char *title,
476be691f3bSpatrick                  chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
477be691f3bSpatrick     Box(bounds, v_char, h_char);
478be691f3bSpatrick     int title_offset = 2;
479be691f3bSpatrick     MoveCursor(bounds.origin.x + title_offset, bounds.origin.y);
480be691f3bSpatrick     PutChar('[');
481be691f3bSpatrick     PutCString(title, bounds.size.width - title_offset);
482be691f3bSpatrick     PutChar(']');
483be691f3bSpatrick   }
484be691f3bSpatrick 
485be691f3bSpatrick   // Curses doesn't allow direct output of color escape sequences, but that's
486be691f3bSpatrick   // how we get source lines from the Highligher class. Read the line and
487be691f3bSpatrick   // convert color escape sequences to curses color attributes. Use
488be691f3bSpatrick   // first_skip_count to skip leading visible characters. Returns false if all
489be691f3bSpatrick   // visible characters were skipped due to first_skip_count.
OutputColoredStringTruncated(int right_pad,StringRef string,size_t skip_first_count,bool use_blue_background)490be691f3bSpatrick   bool OutputColoredStringTruncated(int right_pad, StringRef string,
491be691f3bSpatrick                                     size_t skip_first_count,
492be691f3bSpatrick                                     bool use_blue_background) {
493be691f3bSpatrick     attr_t saved_attr;
494be691f3bSpatrick     short saved_pair;
495be691f3bSpatrick     bool result = false;
496be691f3bSpatrick     wattr_get(m_window, &saved_attr, &saved_pair, nullptr);
497be691f3bSpatrick     if (use_blue_background)
498be691f3bSpatrick       ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
499be691f3bSpatrick     while (!string.empty()) {
500*f6aab3d8Srobert       size_t esc_pos = string.find(ANSI_ESC_START);
501be691f3bSpatrick       if (esc_pos == StringRef::npos) {
502be691f3bSpatrick         string = string.substr(skip_first_count);
503be691f3bSpatrick         if (!string.empty()) {
504be691f3bSpatrick           PutCStringTruncated(right_pad, string.data(), string.size());
505be691f3bSpatrick           result = true;
506be691f3bSpatrick         }
507be691f3bSpatrick         break;
508be691f3bSpatrick       }
509be691f3bSpatrick       if (esc_pos > 0) {
510be691f3bSpatrick         if (skip_first_count > 0) {
511be691f3bSpatrick           int skip = std::min(esc_pos, skip_first_count);
512be691f3bSpatrick           string = string.substr(skip);
513be691f3bSpatrick           skip_first_count -= skip;
514be691f3bSpatrick           esc_pos -= skip;
515be691f3bSpatrick         }
516be691f3bSpatrick         if (esc_pos > 0) {
517be691f3bSpatrick           PutCStringTruncated(right_pad, string.data(), esc_pos);
518be691f3bSpatrick           result = true;
519be691f3bSpatrick           string = string.drop_front(esc_pos);
520be691f3bSpatrick         }
521be691f3bSpatrick       }
522*f6aab3d8Srobert       bool consumed = string.consume_front(ANSI_ESC_START);
523be691f3bSpatrick       assert(consumed);
524be691f3bSpatrick       UNUSED_IF_ASSERT_DISABLED(consumed);
525be691f3bSpatrick       // This is written to match our Highlighter classes, which seem to
526be691f3bSpatrick       // generate only foreground color escape sequences. If necessary, this
527be691f3bSpatrick       // will need to be extended.
528*f6aab3d8Srobert       // Only 8 basic foreground colors, underline and reset, our Highlighter
529*f6aab3d8Srobert       // doesn't use anything else.
530be691f3bSpatrick       int value;
531be691f3bSpatrick       if (!!string.consumeInteger(10, value) || // Returns false on success.
532*f6aab3d8Srobert           !(value == 0 || value == ANSI_CTRL_UNDERLINE ||
533*f6aab3d8Srobert             (value >= ANSI_FG_COLOR_BLACK && value <= ANSI_FG_COLOR_WHITE))) {
534be691f3bSpatrick         llvm::errs() << "No valid color code in color escape sequence.\n";
535be691f3bSpatrick         continue;
536be691f3bSpatrick       }
537*f6aab3d8Srobert       if (!string.consume_front(ANSI_ESC_END)) {
538*f6aab3d8Srobert         llvm::errs() << "Missing '" << ANSI_ESC_END
539*f6aab3d8Srobert                      << "' in color escape sequence.\n";
540be691f3bSpatrick         continue;
541be691f3bSpatrick       }
542be691f3bSpatrick       if (value == 0) { // Reset.
543be691f3bSpatrick         wattr_set(m_window, saved_attr, saved_pair, nullptr);
544be691f3bSpatrick         if (use_blue_background)
545be691f3bSpatrick           ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
546*f6aab3d8Srobert       } else if (value == ANSI_CTRL_UNDERLINE) {
547*f6aab3d8Srobert         ::wattron(m_window, A_UNDERLINE);
548be691f3bSpatrick       } else {
549be691f3bSpatrick         // Mapped directly to first 16 color pairs (black/blue background).
550*f6aab3d8Srobert         ::wattron(m_window, COLOR_PAIR(value - ANSI_FG_COLOR_BLACK + 1 +
551*f6aab3d8Srobert                                        (use_blue_background ? 8 : 0)));
552be691f3bSpatrick       }
553be691f3bSpatrick     }
554be691f3bSpatrick     wattr_set(m_window, saved_attr, saved_pair, nullptr);
555be691f3bSpatrick     return result;
556be691f3bSpatrick   }
557be691f3bSpatrick 
558be691f3bSpatrick protected:
559*f6aab3d8Srobert   Type m_type;
560*f6aab3d8Srobert   WINDOW *m_window = nullptr;
561be691f3bSpatrick };
562be691f3bSpatrick 
563be691f3bSpatrick class Pad : public Surface {
564be691f3bSpatrick public:
Pad(Size size)565*f6aab3d8Srobert   Pad(Size size) : Surface(Surface::Type::Pad) {
566*f6aab3d8Srobert     m_window = ::newpad(size.height, size.width);
567*f6aab3d8Srobert   }
568be691f3bSpatrick 
~Pad()569be691f3bSpatrick   ~Pad() { ::delwin(m_window); }
570be691f3bSpatrick };
571be691f3bSpatrick 
572be691f3bSpatrick class Window : public Surface {
573061da546Spatrick public:
Window(const char * name)574061da546Spatrick   Window(const char *name)
575*f6aab3d8Srobert       : Surface(Surface::Type::Window), m_name(name), m_panel(nullptr),
576*f6aab3d8Srobert         m_parent(nullptr), m_subwindows(), m_delegate_sp(),
577*f6aab3d8Srobert         m_curr_active_window_idx(UINT32_MAX),
578061da546Spatrick         m_prev_active_window_idx(UINT32_MAX), m_delete(false),
579061da546Spatrick         m_needs_update(true), m_can_activate(true), m_is_subwin(false) {}
580061da546Spatrick 
Window(const char * name,WINDOW * w,bool del=true)581061da546Spatrick   Window(const char *name, WINDOW *w, bool del = true)
582*f6aab3d8Srobert       : Surface(Surface::Type::Window), m_name(name), m_panel(nullptr),
583*f6aab3d8Srobert         m_parent(nullptr), m_subwindows(), m_delegate_sp(),
584*f6aab3d8Srobert         m_curr_active_window_idx(UINT32_MAX),
585061da546Spatrick         m_prev_active_window_idx(UINT32_MAX), m_delete(del),
586061da546Spatrick         m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
587061da546Spatrick     if (w)
588061da546Spatrick       Reset(w);
589061da546Spatrick   }
590061da546Spatrick 
Window(const char * name,const Rect & bounds)591061da546Spatrick   Window(const char *name, const Rect &bounds)
592*f6aab3d8Srobert       : Surface(Surface::Type::Window), m_name(name), m_panel(nullptr),
593*f6aab3d8Srobert         m_parent(nullptr), m_subwindows(), m_delegate_sp(),
594be691f3bSpatrick         m_curr_active_window_idx(UINT32_MAX),
595*f6aab3d8Srobert         m_prev_active_window_idx(UINT32_MAX), m_delete(false),
596061da546Spatrick         m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
597061da546Spatrick     Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y,
598061da546Spatrick                    bounds.origin.y));
599061da546Spatrick   }
600061da546Spatrick 
~Window()601061da546Spatrick   virtual ~Window() {
602061da546Spatrick     RemoveSubWindows();
603061da546Spatrick     Reset();
604061da546Spatrick   }
605061da546Spatrick 
Reset(WINDOW * w=nullptr,bool del=true)606061da546Spatrick   void Reset(WINDOW *w = nullptr, bool del = true) {
607061da546Spatrick     if (m_window == w)
608061da546Spatrick       return;
609061da546Spatrick 
610061da546Spatrick     if (m_panel) {
611061da546Spatrick       ::del_panel(m_panel);
612061da546Spatrick       m_panel = nullptr;
613061da546Spatrick     }
614061da546Spatrick     if (m_window && m_delete) {
615061da546Spatrick       ::delwin(m_window);
616061da546Spatrick       m_window = nullptr;
617061da546Spatrick       m_delete = false;
618061da546Spatrick     }
619061da546Spatrick     if (w) {
620061da546Spatrick       m_window = w;
621061da546Spatrick       m_panel = ::new_panel(m_window);
622061da546Spatrick       m_delete = del;
623061da546Spatrick     }
624061da546Spatrick   }
625061da546Spatrick 
626be691f3bSpatrick   // Get the rectangle in our parent window
GetBounds() const627be691f3bSpatrick   Rect GetBounds() const { return Rect(GetParentOrigin(), GetSize()); }
628be691f3bSpatrick 
GetCenteredRect(int width,int height)629be691f3bSpatrick   Rect GetCenteredRect(int width, int height) {
630be691f3bSpatrick     Size size = GetSize();
631be691f3bSpatrick     width = std::min(size.width, width);
632be691f3bSpatrick     height = std::min(size.height, height);
633be691f3bSpatrick     int x = (size.width - width) / 2;
634be691f3bSpatrick     int y = (size.height - height) / 2;
635be691f3bSpatrick     return Rect(Point(x, y), Size(width, height));
636061da546Spatrick   }
637be691f3bSpatrick 
GetChar()638061da546Spatrick   int GetChar() { return ::wgetch(m_window); }
GetParentOrigin() const639be691f3bSpatrick   Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); }
GetParentX() const640be691f3bSpatrick   int GetParentX() const { return getparx(m_window); }
GetParentY() const641be691f3bSpatrick   int GetParentY() const { return getpary(m_window); }
MoveWindow(int x,int y)642061da546Spatrick   void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); }
Resize(int w,int h)643061da546Spatrick   void Resize(int w, int h) { ::wresize(m_window, h, w); }
Resize(const Size & size)644061da546Spatrick   void Resize(const Size &size) {
645061da546Spatrick     ::wresize(m_window, size.height, size.width);
646061da546Spatrick   }
MoveWindow(const Point & origin)647061da546Spatrick   void MoveWindow(const Point &origin) {
648061da546Spatrick     const bool moving_window = origin != GetParentOrigin();
649061da546Spatrick     if (m_is_subwin && moving_window) {
650061da546Spatrick       // Can't move subwindows, must delete and re-create
651061da546Spatrick       Size size = GetSize();
652061da546Spatrick       Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y,
653061da546Spatrick                      origin.x),
654061da546Spatrick             true);
655061da546Spatrick     } else {
656061da546Spatrick       ::mvwin(m_window, origin.y, origin.x);
657061da546Spatrick     }
658061da546Spatrick   }
659061da546Spatrick 
SetBounds(const Rect & bounds)660061da546Spatrick   void SetBounds(const Rect &bounds) {
661061da546Spatrick     const bool moving_window = bounds.origin != GetParentOrigin();
662061da546Spatrick     if (m_is_subwin && moving_window) {
663061da546Spatrick       // Can't move subwindows, must delete and re-create
664061da546Spatrick       Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width,
665061da546Spatrick                      bounds.origin.y, bounds.origin.x),
666061da546Spatrick             true);
667061da546Spatrick     } else {
668061da546Spatrick       if (moving_window)
669061da546Spatrick         MoveWindow(bounds.origin);
670061da546Spatrick       Resize(bounds.size);
671061da546Spatrick     }
672061da546Spatrick   }
673061da546Spatrick 
Touch()674061da546Spatrick   void Touch() {
675061da546Spatrick     ::touchwin(m_window);
676061da546Spatrick     if (m_parent)
677061da546Spatrick       m_parent->Touch();
678061da546Spatrick   }
679061da546Spatrick 
CreateSubWindow(const char * name,const Rect & bounds,bool make_active)680061da546Spatrick   WindowSP CreateSubWindow(const char *name, const Rect &bounds,
681061da546Spatrick                            bool make_active) {
682061da546Spatrick     auto get_window = [this, &bounds]() {
683061da546Spatrick       return m_window
684061da546Spatrick                  ? ::subwin(m_window, bounds.size.height, bounds.size.width,
685061da546Spatrick                             bounds.origin.y, bounds.origin.x)
686061da546Spatrick                  : ::newwin(bounds.size.height, bounds.size.width,
687061da546Spatrick                             bounds.origin.y, bounds.origin.x);
688061da546Spatrick     };
689061da546Spatrick     WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true);
690061da546Spatrick     subwindow_sp->m_is_subwin = subwindow_sp.operator bool();
691061da546Spatrick     subwindow_sp->m_parent = this;
692061da546Spatrick     if (make_active) {
693061da546Spatrick       m_prev_active_window_idx = m_curr_active_window_idx;
694061da546Spatrick       m_curr_active_window_idx = m_subwindows.size();
695061da546Spatrick     }
696061da546Spatrick     m_subwindows.push_back(subwindow_sp);
697061da546Spatrick     ::top_panel(subwindow_sp->m_panel);
698061da546Spatrick     m_needs_update = true;
699061da546Spatrick     return subwindow_sp;
700061da546Spatrick   }
701061da546Spatrick 
RemoveSubWindow(Window * window)702061da546Spatrick   bool RemoveSubWindow(Window *window) {
703061da546Spatrick     Windows::iterator pos, end = m_subwindows.end();
704061da546Spatrick     size_t i = 0;
705061da546Spatrick     for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
706061da546Spatrick       if ((*pos).get() == window) {
707061da546Spatrick         if (m_prev_active_window_idx == i)
708061da546Spatrick           m_prev_active_window_idx = UINT32_MAX;
709061da546Spatrick         else if (m_prev_active_window_idx != UINT32_MAX &&
710061da546Spatrick                  m_prev_active_window_idx > i)
711061da546Spatrick           --m_prev_active_window_idx;
712061da546Spatrick 
713061da546Spatrick         if (m_curr_active_window_idx == i)
714061da546Spatrick           m_curr_active_window_idx = UINT32_MAX;
715061da546Spatrick         else if (m_curr_active_window_idx != UINT32_MAX &&
716061da546Spatrick                  m_curr_active_window_idx > i)
717061da546Spatrick           --m_curr_active_window_idx;
718061da546Spatrick         window->Erase();
719061da546Spatrick         m_subwindows.erase(pos);
720061da546Spatrick         m_needs_update = true;
721061da546Spatrick         if (m_parent)
722061da546Spatrick           m_parent->Touch();
723061da546Spatrick         else
724061da546Spatrick           ::touchwin(stdscr);
725061da546Spatrick         return true;
726061da546Spatrick       }
727061da546Spatrick     }
728061da546Spatrick     return false;
729061da546Spatrick   }
730061da546Spatrick 
FindSubWindow(const char * name)731061da546Spatrick   WindowSP FindSubWindow(const char *name) {
732061da546Spatrick     Windows::iterator pos, end = m_subwindows.end();
733061da546Spatrick     size_t i = 0;
734061da546Spatrick     for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
735061da546Spatrick       if ((*pos)->m_name == name)
736061da546Spatrick         return *pos;
737061da546Spatrick     }
738061da546Spatrick     return WindowSP();
739061da546Spatrick   }
740061da546Spatrick 
RemoveSubWindows()741061da546Spatrick   void RemoveSubWindows() {
742061da546Spatrick     m_curr_active_window_idx = UINT32_MAX;
743061da546Spatrick     m_prev_active_window_idx = UINT32_MAX;
744061da546Spatrick     for (Windows::iterator pos = m_subwindows.begin();
745061da546Spatrick          pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) {
746061da546Spatrick       (*pos)->Erase();
747061da546Spatrick     }
748061da546Spatrick     if (m_parent)
749061da546Spatrick       m_parent->Touch();
750061da546Spatrick     else
751061da546Spatrick       ::touchwin(stdscr);
752061da546Spatrick   }
753061da546Spatrick 
754061da546Spatrick   // Window drawing utilities
DrawTitleBox(const char * title,const char * bottom_message=nullptr)755061da546Spatrick   void DrawTitleBox(const char *title, const char *bottom_message = nullptr) {
756061da546Spatrick     attr_t attr = 0;
757061da546Spatrick     if (IsActive())
758be691f3bSpatrick       attr = A_BOLD | COLOR_PAIR(BlackOnWhite);
759061da546Spatrick     else
760061da546Spatrick       attr = 0;
761061da546Spatrick     if (attr)
762061da546Spatrick       AttributeOn(attr);
763061da546Spatrick 
764061da546Spatrick     Box();
765061da546Spatrick     MoveCursor(3, 0);
766061da546Spatrick 
767061da546Spatrick     if (title && title[0]) {
768061da546Spatrick       PutChar('<');
769061da546Spatrick       PutCString(title);
770061da546Spatrick       PutChar('>');
771061da546Spatrick     }
772061da546Spatrick 
773061da546Spatrick     if (bottom_message && bottom_message[0]) {
774061da546Spatrick       int bottom_message_length = strlen(bottom_message);
775061da546Spatrick       int x = GetWidth() - 3 - (bottom_message_length + 2);
776061da546Spatrick 
777061da546Spatrick       if (x > 0) {
778061da546Spatrick         MoveCursor(x, GetHeight() - 1);
779061da546Spatrick         PutChar('[');
780061da546Spatrick         PutCString(bottom_message);
781061da546Spatrick         PutChar(']');
782061da546Spatrick       } else {
783061da546Spatrick         MoveCursor(1, GetHeight() - 1);
784061da546Spatrick         PutChar('[');
785be691f3bSpatrick         PutCStringTruncated(1, bottom_message);
786061da546Spatrick       }
787061da546Spatrick     }
788061da546Spatrick     if (attr)
789061da546Spatrick       AttributeOff(attr);
790061da546Spatrick   }
791061da546Spatrick 
Draw(bool force)792061da546Spatrick   virtual void Draw(bool force) {
793061da546Spatrick     if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force))
794061da546Spatrick       return;
795061da546Spatrick 
796061da546Spatrick     for (auto &subwindow_sp : m_subwindows)
797061da546Spatrick       subwindow_sp->Draw(force);
798061da546Spatrick   }
799061da546Spatrick 
CreateHelpSubwindow()800061da546Spatrick   bool CreateHelpSubwindow() {
801061da546Spatrick     if (m_delegate_sp) {
802061da546Spatrick       const char *text = m_delegate_sp->WindowDelegateGetHelpText();
803061da546Spatrick       KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp();
804061da546Spatrick       if ((text && text[0]) || key_help) {
805061da546Spatrick         std::unique_ptr<HelpDialogDelegate> help_delegate_up(
806061da546Spatrick             new HelpDialogDelegate(text, key_help));
807061da546Spatrick         const size_t num_lines = help_delegate_up->GetNumLines();
808061da546Spatrick         const size_t max_length = help_delegate_up->GetMaxLineLength();
809061da546Spatrick         Rect bounds = GetBounds();
810061da546Spatrick         bounds.Inset(1, 1);
811061da546Spatrick         if (max_length + 4 < static_cast<size_t>(bounds.size.width)) {
812061da546Spatrick           bounds.origin.x += (bounds.size.width - max_length + 4) / 2;
813061da546Spatrick           bounds.size.width = max_length + 4;
814061da546Spatrick         } else {
815061da546Spatrick           if (bounds.size.width > 100) {
816061da546Spatrick             const int inset_w = bounds.size.width / 4;
817061da546Spatrick             bounds.origin.x += inset_w;
818061da546Spatrick             bounds.size.width -= 2 * inset_w;
819061da546Spatrick           }
820061da546Spatrick         }
821061da546Spatrick 
822061da546Spatrick         if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) {
823061da546Spatrick           bounds.origin.y += (bounds.size.height - num_lines + 2) / 2;
824061da546Spatrick           bounds.size.height = num_lines + 2;
825061da546Spatrick         } else {
826061da546Spatrick           if (bounds.size.height > 100) {
827061da546Spatrick             const int inset_h = bounds.size.height / 4;
828061da546Spatrick             bounds.origin.y += inset_h;
829061da546Spatrick             bounds.size.height -= 2 * inset_h;
830061da546Spatrick           }
831061da546Spatrick         }
832061da546Spatrick         WindowSP help_window_sp;
833061da546Spatrick         Window *parent_window = GetParent();
834061da546Spatrick         if (parent_window)
835061da546Spatrick           help_window_sp = parent_window->CreateSubWindow("Help", bounds, true);
836061da546Spatrick         else
837061da546Spatrick           help_window_sp = CreateSubWindow("Help", bounds, true);
838061da546Spatrick         help_window_sp->SetDelegate(
839061da546Spatrick             WindowDelegateSP(help_delegate_up.release()));
840061da546Spatrick         return true;
841061da546Spatrick       }
842061da546Spatrick     }
843061da546Spatrick     return false;
844061da546Spatrick   }
845061da546Spatrick 
HandleChar(int key)846061da546Spatrick   virtual HandleCharResult HandleChar(int key) {
847061da546Spatrick     // Always check the active window first
848061da546Spatrick     HandleCharResult result = eKeyNotHandled;
849061da546Spatrick     WindowSP active_window_sp = GetActiveWindow();
850061da546Spatrick     if (active_window_sp) {
851061da546Spatrick       result = active_window_sp->HandleChar(key);
852061da546Spatrick       if (result != eKeyNotHandled)
853061da546Spatrick         return result;
854061da546Spatrick     }
855061da546Spatrick 
856061da546Spatrick     if (m_delegate_sp) {
857061da546Spatrick       result = m_delegate_sp->WindowDelegateHandleChar(*this, key);
858061da546Spatrick       if (result != eKeyNotHandled)
859061da546Spatrick         return result;
860061da546Spatrick     }
861061da546Spatrick 
862061da546Spatrick     // Then check for any windows that want any keys that weren't handled. This
863061da546Spatrick     // is typically only for a menubar. Make a copy of the subwindows in case
864061da546Spatrick     // any HandleChar() functions muck with the subwindows. If we don't do
865061da546Spatrick     // this, we can crash when iterating over the subwindows.
866061da546Spatrick     Windows subwindows(m_subwindows);
867061da546Spatrick     for (auto subwindow_sp : subwindows) {
868061da546Spatrick       if (!subwindow_sp->m_can_activate) {
869061da546Spatrick         HandleCharResult result = subwindow_sp->HandleChar(key);
870061da546Spatrick         if (result != eKeyNotHandled)
871061da546Spatrick           return result;
872061da546Spatrick       }
873061da546Spatrick     }
874061da546Spatrick 
875061da546Spatrick     return eKeyNotHandled;
876061da546Spatrick   }
877061da546Spatrick 
GetActiveWindow()878061da546Spatrick   WindowSP GetActiveWindow() {
879061da546Spatrick     if (!m_subwindows.empty()) {
880061da546Spatrick       if (m_curr_active_window_idx >= m_subwindows.size()) {
881061da546Spatrick         if (m_prev_active_window_idx < m_subwindows.size()) {
882061da546Spatrick           m_curr_active_window_idx = m_prev_active_window_idx;
883061da546Spatrick           m_prev_active_window_idx = UINT32_MAX;
884061da546Spatrick         } else if (IsActive()) {
885061da546Spatrick           m_prev_active_window_idx = UINT32_MAX;
886061da546Spatrick           m_curr_active_window_idx = UINT32_MAX;
887061da546Spatrick 
888061da546Spatrick           // Find first window that wants to be active if this window is active
889061da546Spatrick           const size_t num_subwindows = m_subwindows.size();
890061da546Spatrick           for (size_t i = 0; i < num_subwindows; ++i) {
891061da546Spatrick             if (m_subwindows[i]->GetCanBeActive()) {
892061da546Spatrick               m_curr_active_window_idx = i;
893061da546Spatrick               break;
894061da546Spatrick             }
895061da546Spatrick           }
896061da546Spatrick         }
897061da546Spatrick       }
898061da546Spatrick 
899061da546Spatrick       if (m_curr_active_window_idx < m_subwindows.size())
900061da546Spatrick         return m_subwindows[m_curr_active_window_idx];
901061da546Spatrick     }
902061da546Spatrick     return WindowSP();
903061da546Spatrick   }
904061da546Spatrick 
GetCanBeActive() const905061da546Spatrick   bool GetCanBeActive() const { return m_can_activate; }
906061da546Spatrick 
SetCanBeActive(bool b)907061da546Spatrick   void SetCanBeActive(bool b) { m_can_activate = b; }
908061da546Spatrick 
SetDelegate(const WindowDelegateSP & delegate_sp)909061da546Spatrick   void SetDelegate(const WindowDelegateSP &delegate_sp) {
910061da546Spatrick     m_delegate_sp = delegate_sp;
911061da546Spatrick   }
912061da546Spatrick 
GetParent() const913061da546Spatrick   Window *GetParent() const { return m_parent; }
914061da546Spatrick 
IsActive() const915061da546Spatrick   bool IsActive() const {
916061da546Spatrick     if (m_parent)
917061da546Spatrick       return m_parent->GetActiveWindow().get() == this;
918061da546Spatrick     else
919061da546Spatrick       return true; // Top level window is always active
920061da546Spatrick   }
921061da546Spatrick 
SelectNextWindowAsActive()922061da546Spatrick   void SelectNextWindowAsActive() {
923061da546Spatrick     // Move active focus to next window
924be691f3bSpatrick     const int num_subwindows = m_subwindows.size();
925be691f3bSpatrick     int start_idx = 0;
926be691f3bSpatrick     if (m_curr_active_window_idx != UINT32_MAX) {
927061da546Spatrick       m_prev_active_window_idx = m_curr_active_window_idx;
928be691f3bSpatrick       start_idx = m_curr_active_window_idx + 1;
929be691f3bSpatrick     }
930be691f3bSpatrick     for (int idx = start_idx; idx < num_subwindows; ++idx) {
931061da546Spatrick       if (m_subwindows[idx]->GetCanBeActive()) {
932061da546Spatrick         m_curr_active_window_idx = idx;
933be691f3bSpatrick         return;
934061da546Spatrick       }
935061da546Spatrick     }
936be691f3bSpatrick     for (int idx = 0; idx < start_idx; ++idx) {
937061da546Spatrick       if (m_subwindows[idx]->GetCanBeActive()) {
938061da546Spatrick         m_curr_active_window_idx = idx;
939061da546Spatrick         break;
940061da546Spatrick       }
941061da546Spatrick     }
942061da546Spatrick   }
943be691f3bSpatrick 
SelectPreviousWindowAsActive()944be691f3bSpatrick   void SelectPreviousWindowAsActive() {
945be691f3bSpatrick     // Move active focus to previous window
946be691f3bSpatrick     const int num_subwindows = m_subwindows.size();
947be691f3bSpatrick     int start_idx = num_subwindows - 1;
948be691f3bSpatrick     if (m_curr_active_window_idx != UINT32_MAX) {
949061da546Spatrick       m_prev_active_window_idx = m_curr_active_window_idx;
950be691f3bSpatrick       start_idx = m_curr_active_window_idx - 1;
951be691f3bSpatrick     }
952be691f3bSpatrick     for (int idx = start_idx; idx >= 0; --idx) {
953be691f3bSpatrick       if (m_subwindows[idx]->GetCanBeActive()) {
954be691f3bSpatrick         m_curr_active_window_idx = idx;
955be691f3bSpatrick         return;
956be691f3bSpatrick       }
957be691f3bSpatrick     }
958be691f3bSpatrick     for (int idx = num_subwindows - 1; idx > start_idx; --idx) {
959061da546Spatrick       if (m_subwindows[idx]->GetCanBeActive()) {
960061da546Spatrick         m_curr_active_window_idx = idx;
961061da546Spatrick         break;
962061da546Spatrick       }
963061da546Spatrick     }
964061da546Spatrick   }
965061da546Spatrick 
GetName() const966061da546Spatrick   const char *GetName() const { return m_name.c_str(); }
967061da546Spatrick 
968061da546Spatrick protected:
969061da546Spatrick   std::string m_name;
970061da546Spatrick   PANEL *m_panel;
971061da546Spatrick   Window *m_parent;
972061da546Spatrick   Windows m_subwindows;
973061da546Spatrick   WindowDelegateSP m_delegate_sp;
974061da546Spatrick   uint32_t m_curr_active_window_idx;
975061da546Spatrick   uint32_t m_prev_active_window_idx;
976061da546Spatrick   bool m_delete;
977061da546Spatrick   bool m_needs_update;
978061da546Spatrick   bool m_can_activate;
979061da546Spatrick   bool m_is_subwin;
980061da546Spatrick 
981061da546Spatrick private:
982dda28197Spatrick   Window(const Window &) = delete;
983dda28197Spatrick   const Window &operator=(const Window &) = delete;
984061da546Spatrick };
985061da546Spatrick 
986be691f3bSpatrick /////////
987be691f3bSpatrick // Forms
988be691f3bSpatrick /////////
989be691f3bSpatrick 
990be691f3bSpatrick // A scroll context defines a vertical region that needs to be visible in a
991be691f3bSpatrick // scrolling area. The region is defined by the index of the start and end lines
992be691f3bSpatrick // of the region. The start and end lines may be equal, in which case, the
993be691f3bSpatrick // region is a single line.
994be691f3bSpatrick struct ScrollContext {
995be691f3bSpatrick   int start;
996be691f3bSpatrick   int end;
997be691f3bSpatrick 
ScrollContextcurses::ScrollContext998be691f3bSpatrick   ScrollContext(int line) : start(line), end(line) {}
ScrollContextcurses::ScrollContext999be691f3bSpatrick   ScrollContext(int _start, int _end) : start(_start), end(_end) {}
1000be691f3bSpatrick 
Offsetcurses::ScrollContext1001be691f3bSpatrick   void Offset(int offset) {
1002be691f3bSpatrick     start += offset;
1003be691f3bSpatrick     end += offset;
1004be691f3bSpatrick   }
1005be691f3bSpatrick };
1006be691f3bSpatrick 
1007be691f3bSpatrick class FieldDelegate {
1008be691f3bSpatrick public:
1009be691f3bSpatrick   virtual ~FieldDelegate() = default;
1010be691f3bSpatrick 
1011be691f3bSpatrick   // Returns the number of lines needed to draw the field. The draw method will
1012be691f3bSpatrick   // be given a surface that have exactly this number of lines.
1013be691f3bSpatrick   virtual int FieldDelegateGetHeight() = 0;
1014be691f3bSpatrick 
1015be691f3bSpatrick   // Returns the scroll context in the local coordinates of the field. By
1016be691f3bSpatrick   // default, the scroll context spans the whole field. Bigger fields with
1017be691f3bSpatrick   // internal navigation should override this method to provide a finer context.
1018be691f3bSpatrick   // Typical override methods would first get the scroll context of the internal
1019be691f3bSpatrick   // element then add the offset of the element in the field.
FieldDelegateGetScrollContext()1020be691f3bSpatrick   virtual ScrollContext FieldDelegateGetScrollContext() {
1021be691f3bSpatrick     return ScrollContext(0, FieldDelegateGetHeight() - 1);
1022be691f3bSpatrick   }
1023be691f3bSpatrick 
1024be691f3bSpatrick   // Draw the field in the given subpad surface. The surface have a height that
1025be691f3bSpatrick   // is equal to the height returned by FieldDelegateGetHeight(). If the field
1026be691f3bSpatrick   // is selected in the form window, then is_selected will be true.
1027*f6aab3d8Srobert   virtual void FieldDelegateDraw(Surface &surface, bool is_selected) = 0;
1028be691f3bSpatrick 
1029be691f3bSpatrick   // Handle the key that wasn't handled by the form window or a container field.
FieldDelegateHandleChar(int key)1030be691f3bSpatrick   virtual HandleCharResult FieldDelegateHandleChar(int key) {
1031be691f3bSpatrick     return eKeyNotHandled;
1032be691f3bSpatrick   }
1033be691f3bSpatrick 
1034be691f3bSpatrick   // This is executed once the user exists the field, that is, once the user
1035be691f3bSpatrick   // navigates to the next or the previous field. This is particularly useful to
1036be691f3bSpatrick   // do in-field validation and error setting. Fields with internal navigation
1037be691f3bSpatrick   // should call this method on their fields.
FieldDelegateExitCallback()1038*f6aab3d8Srobert   virtual void FieldDelegateExitCallback() {}
1039be691f3bSpatrick 
1040be691f3bSpatrick   // Fields may have internal navigation, for instance, a List Field have
1041be691f3bSpatrick   // multiple internal elements, which needs to be navigated. To allow for this
1042be691f3bSpatrick   // mechanism, the window shouldn't handle the navigation keys all the time,
1043be691f3bSpatrick   // and instead call the key handing method of the selected field. It should
1044be691f3bSpatrick   // only handle the navigation keys when the field contains a single element or
1045be691f3bSpatrick   // have the last or first element selected depending on if the user is
1046be691f3bSpatrick   // navigating forward or backward. Additionally, once a field is selected in
1047be691f3bSpatrick   // the forward or backward direction, its first or last internal element
1048be691f3bSpatrick   // should be selected. The following methods implements those mechanisms.
1049be691f3bSpatrick 
1050be691f3bSpatrick   // Returns true if the first element in the field is selected or if the field
1051be691f3bSpatrick   // contains a single element.
FieldDelegateOnFirstOrOnlyElement()1052be691f3bSpatrick   virtual bool FieldDelegateOnFirstOrOnlyElement() { return true; }
1053be691f3bSpatrick 
1054be691f3bSpatrick   // Returns true if the last element in the field is selected or if the field
1055be691f3bSpatrick   // contains a single element.
FieldDelegateOnLastOrOnlyElement()1056be691f3bSpatrick   virtual bool FieldDelegateOnLastOrOnlyElement() { return true; }
1057be691f3bSpatrick 
1058be691f3bSpatrick   // Select the first element in the field if multiple elements exists.
FieldDelegateSelectFirstElement()1059*f6aab3d8Srobert   virtual void FieldDelegateSelectFirstElement() {}
1060be691f3bSpatrick 
1061be691f3bSpatrick   // Select the last element in the field if multiple elements exists.
FieldDelegateSelectLastElement()1062*f6aab3d8Srobert   virtual void FieldDelegateSelectLastElement() {}
1063be691f3bSpatrick 
1064be691f3bSpatrick   // Returns true if the field has an error, false otherwise.
FieldDelegateHasError()1065be691f3bSpatrick   virtual bool FieldDelegateHasError() { return false; }
1066be691f3bSpatrick 
FieldDelegateIsVisible()1067be691f3bSpatrick   bool FieldDelegateIsVisible() { return m_is_visible; }
1068be691f3bSpatrick 
FieldDelegateHide()1069be691f3bSpatrick   void FieldDelegateHide() { m_is_visible = false; }
1070be691f3bSpatrick 
FieldDelegateShow()1071be691f3bSpatrick   void FieldDelegateShow() { m_is_visible = true; }
1072be691f3bSpatrick 
1073be691f3bSpatrick protected:
1074be691f3bSpatrick   bool m_is_visible = true;
1075be691f3bSpatrick };
1076be691f3bSpatrick 
1077be691f3bSpatrick typedef std::unique_ptr<FieldDelegate> FieldDelegateUP;
1078be691f3bSpatrick 
1079be691f3bSpatrick class TextFieldDelegate : public FieldDelegate {
1080be691f3bSpatrick public:
TextFieldDelegate(const char * label,const char * content,bool required)1081be691f3bSpatrick   TextFieldDelegate(const char *label, const char *content, bool required)
1082*f6aab3d8Srobert       : m_label(label), m_required(required) {
1083be691f3bSpatrick     if (content)
1084be691f3bSpatrick       m_content = content;
1085be691f3bSpatrick   }
1086be691f3bSpatrick 
1087be691f3bSpatrick   // Text fields are drawn as titled boxes of a single line, with a possible
1088be691f3bSpatrick   // error messages at the end.
1089be691f3bSpatrick   //
1090be691f3bSpatrick   // __[Label]___________
1091be691f3bSpatrick   // |                  |
1092be691f3bSpatrick   // |__________________|
1093be691f3bSpatrick   // - Error message if it exists.
1094be691f3bSpatrick 
1095be691f3bSpatrick   // The text field has a height of 3 lines. 2 lines for borders and 1 line for
1096be691f3bSpatrick   // the content.
GetFieldHeight()1097be691f3bSpatrick   int GetFieldHeight() { return 3; }
1098be691f3bSpatrick 
1099be691f3bSpatrick   // The text field has a full height of 3 or 4 lines. 3 lines for the actual
1100be691f3bSpatrick   // field and an optional line for an error if it exists.
FieldDelegateGetHeight()1101be691f3bSpatrick   int FieldDelegateGetHeight() override {
1102be691f3bSpatrick     int height = GetFieldHeight();
1103be691f3bSpatrick     if (FieldDelegateHasError())
1104be691f3bSpatrick       height++;
1105be691f3bSpatrick     return height;
1106be691f3bSpatrick   }
1107be691f3bSpatrick 
1108be691f3bSpatrick   // Get the cursor X position in the surface coordinate.
GetCursorXPosition()1109be691f3bSpatrick   int GetCursorXPosition() { return m_cursor_position - m_first_visibile_char; }
1110be691f3bSpatrick 
GetContentLength()1111be691f3bSpatrick   int GetContentLength() { return m_content.length(); }
1112be691f3bSpatrick 
DrawContent(Surface & surface,bool is_selected)1113*f6aab3d8Srobert   void DrawContent(Surface &surface, bool is_selected) {
1114*f6aab3d8Srobert     UpdateScrolling(surface.GetWidth());
1115*f6aab3d8Srobert 
1116be691f3bSpatrick     surface.MoveCursor(0, 0);
1117be691f3bSpatrick     const char *text = m_content.c_str() + m_first_visibile_char;
1118be691f3bSpatrick     surface.PutCString(text, surface.GetWidth());
1119be691f3bSpatrick 
1120be691f3bSpatrick     // Highlight the cursor.
1121be691f3bSpatrick     surface.MoveCursor(GetCursorXPosition(), 0);
1122be691f3bSpatrick     if (is_selected)
1123be691f3bSpatrick       surface.AttributeOn(A_REVERSE);
1124be691f3bSpatrick     if (m_cursor_position == GetContentLength())
1125be691f3bSpatrick       // Cursor is past the last character. Highlight an empty space.
1126be691f3bSpatrick       surface.PutChar(' ');
1127be691f3bSpatrick     else
1128be691f3bSpatrick       surface.PutChar(m_content[m_cursor_position]);
1129be691f3bSpatrick     if (is_selected)
1130be691f3bSpatrick       surface.AttributeOff(A_REVERSE);
1131be691f3bSpatrick   }
1132be691f3bSpatrick 
DrawField(Surface & surface,bool is_selected)1133*f6aab3d8Srobert   void DrawField(Surface &surface, bool is_selected) {
1134be691f3bSpatrick     surface.TitledBox(m_label.c_str());
1135be691f3bSpatrick 
1136be691f3bSpatrick     Rect content_bounds = surface.GetFrame();
1137be691f3bSpatrick     content_bounds.Inset(1, 1);
1138*f6aab3d8Srobert     Surface content_surface = surface.SubSurface(content_bounds);
1139be691f3bSpatrick 
1140be691f3bSpatrick     DrawContent(content_surface, is_selected);
1141be691f3bSpatrick   }
1142be691f3bSpatrick 
DrawError(Surface & surface)1143*f6aab3d8Srobert   void DrawError(Surface &surface) {
1144be691f3bSpatrick     if (!FieldDelegateHasError())
1145be691f3bSpatrick       return;
1146be691f3bSpatrick     surface.MoveCursor(0, 0);
1147be691f3bSpatrick     surface.AttributeOn(COLOR_PAIR(RedOnBlack));
1148be691f3bSpatrick     surface.PutChar(ACS_DIAMOND);
1149be691f3bSpatrick     surface.PutChar(' ');
1150be691f3bSpatrick     surface.PutCStringTruncated(1, GetError().c_str());
1151be691f3bSpatrick     surface.AttributeOff(COLOR_PAIR(RedOnBlack));
1152be691f3bSpatrick   }
1153be691f3bSpatrick 
FieldDelegateDraw(Surface & surface,bool is_selected)1154*f6aab3d8Srobert   void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1155be691f3bSpatrick     Rect frame = surface.GetFrame();
1156be691f3bSpatrick     Rect field_bounds, error_bounds;
1157be691f3bSpatrick     frame.HorizontalSplit(GetFieldHeight(), field_bounds, error_bounds);
1158*f6aab3d8Srobert     Surface field_surface = surface.SubSurface(field_bounds);
1159*f6aab3d8Srobert     Surface error_surface = surface.SubSurface(error_bounds);
1160be691f3bSpatrick 
1161be691f3bSpatrick     DrawField(field_surface, is_selected);
1162be691f3bSpatrick     DrawError(error_surface);
1163be691f3bSpatrick   }
1164be691f3bSpatrick 
1165*f6aab3d8Srobert   // Get the position of the last visible character.
GetLastVisibleCharPosition(int width)1166*f6aab3d8Srobert   int GetLastVisibleCharPosition(int width) {
1167*f6aab3d8Srobert     int position = m_first_visibile_char + width - 1;
1168*f6aab3d8Srobert     return std::min(position, GetContentLength());
1169*f6aab3d8Srobert   }
1170*f6aab3d8Srobert 
UpdateScrolling(int width)1171*f6aab3d8Srobert   void UpdateScrolling(int width) {
1172*f6aab3d8Srobert     if (m_cursor_position < m_first_visibile_char) {
1173*f6aab3d8Srobert       m_first_visibile_char = m_cursor_position;
1174*f6aab3d8Srobert       return;
1175*f6aab3d8Srobert     }
1176*f6aab3d8Srobert 
1177*f6aab3d8Srobert     if (m_cursor_position > GetLastVisibleCharPosition(width))
1178*f6aab3d8Srobert       m_first_visibile_char = m_cursor_position - (width - 1);
1179*f6aab3d8Srobert   }
1180*f6aab3d8Srobert 
1181be691f3bSpatrick   // The cursor is allowed to move one character past the string.
1182be691f3bSpatrick   // m_cursor_position is in range [0, GetContentLength()].
MoveCursorRight()1183be691f3bSpatrick   void MoveCursorRight() {
1184be691f3bSpatrick     if (m_cursor_position < GetContentLength())
1185be691f3bSpatrick       m_cursor_position++;
1186be691f3bSpatrick   }
1187be691f3bSpatrick 
MoveCursorLeft()1188be691f3bSpatrick   void MoveCursorLeft() {
1189be691f3bSpatrick     if (m_cursor_position > 0)
1190be691f3bSpatrick       m_cursor_position--;
1191be691f3bSpatrick   }
1192be691f3bSpatrick 
MoveCursorToStart()1193*f6aab3d8Srobert   void MoveCursorToStart() { m_cursor_position = 0; }
1194*f6aab3d8Srobert 
MoveCursorToEnd()1195*f6aab3d8Srobert   void MoveCursorToEnd() { m_cursor_position = GetContentLength(); }
1196be691f3bSpatrick 
ScrollLeft()1197be691f3bSpatrick   void ScrollLeft() {
1198be691f3bSpatrick     if (m_first_visibile_char > 0)
1199be691f3bSpatrick       m_first_visibile_char--;
1200be691f3bSpatrick   }
1201be691f3bSpatrick 
1202*f6aab3d8Srobert   // Insert a character at the current cursor position and advance the cursor
1203*f6aab3d8Srobert   // position.
InsertChar(char character)1204be691f3bSpatrick   void InsertChar(char character) {
1205be691f3bSpatrick     m_content.insert(m_cursor_position, 1, character);
1206be691f3bSpatrick     m_cursor_position++;
1207*f6aab3d8Srobert     ClearError();
1208be691f3bSpatrick   }
1209be691f3bSpatrick 
1210be691f3bSpatrick   // Remove the character before the cursor position, retreat the cursor
1211*f6aab3d8Srobert   // position, and scroll left.
RemovePreviousChar()1212*f6aab3d8Srobert   void RemovePreviousChar() {
1213be691f3bSpatrick     if (m_cursor_position == 0)
1214be691f3bSpatrick       return;
1215be691f3bSpatrick 
1216be691f3bSpatrick     m_content.erase(m_cursor_position - 1, 1);
1217be691f3bSpatrick     m_cursor_position--;
1218be691f3bSpatrick     ScrollLeft();
1219*f6aab3d8Srobert     ClearError();
1220*f6aab3d8Srobert   }
1221*f6aab3d8Srobert 
1222*f6aab3d8Srobert   // Remove the character after the cursor position.
RemoveNextChar()1223*f6aab3d8Srobert   void RemoveNextChar() {
1224*f6aab3d8Srobert     if (m_cursor_position == GetContentLength())
1225*f6aab3d8Srobert       return;
1226*f6aab3d8Srobert 
1227*f6aab3d8Srobert     m_content.erase(m_cursor_position, 1);
1228*f6aab3d8Srobert     ClearError();
1229*f6aab3d8Srobert   }
1230*f6aab3d8Srobert 
1231*f6aab3d8Srobert   // Clear characters from the current cursor position to the end.
ClearToEnd()1232*f6aab3d8Srobert   void ClearToEnd() {
1233*f6aab3d8Srobert     m_content.erase(m_cursor_position);
1234*f6aab3d8Srobert     ClearError();
1235*f6aab3d8Srobert   }
1236*f6aab3d8Srobert 
Clear()1237*f6aab3d8Srobert   void Clear() {
1238*f6aab3d8Srobert     m_content.clear();
1239*f6aab3d8Srobert     m_cursor_position = 0;
1240*f6aab3d8Srobert     ClearError();
1241be691f3bSpatrick   }
1242be691f3bSpatrick 
1243be691f3bSpatrick   // True if the key represents a char that can be inserted in the field
1244be691f3bSpatrick   // content, false otherwise.
IsAcceptableChar(int key)1245*f6aab3d8Srobert   virtual bool IsAcceptableChar(int key) {
1246*f6aab3d8Srobert     // The behavior of isprint is undefined when the value is not representable
1247*f6aab3d8Srobert     // as an unsigned char. So explicitly check for non-ascii key codes.
1248*f6aab3d8Srobert     if (key > 127)
1249*f6aab3d8Srobert       return false;
1250*f6aab3d8Srobert     return isprint(key);
1251*f6aab3d8Srobert   }
1252be691f3bSpatrick 
FieldDelegateHandleChar(int key)1253be691f3bSpatrick   HandleCharResult FieldDelegateHandleChar(int key) override {
1254be691f3bSpatrick     if (IsAcceptableChar(key)) {
1255be691f3bSpatrick       ClearError();
1256be691f3bSpatrick       InsertChar((char)key);
1257be691f3bSpatrick       return eKeyHandled;
1258be691f3bSpatrick     }
1259be691f3bSpatrick 
1260be691f3bSpatrick     switch (key) {
1261*f6aab3d8Srobert     case KEY_HOME:
1262*f6aab3d8Srobert     case KEY_CTRL_A:
1263*f6aab3d8Srobert       MoveCursorToStart();
1264*f6aab3d8Srobert       return eKeyHandled;
1265*f6aab3d8Srobert     case KEY_END:
1266*f6aab3d8Srobert     case KEY_CTRL_E:
1267*f6aab3d8Srobert       MoveCursorToEnd();
1268*f6aab3d8Srobert       return eKeyHandled;
1269be691f3bSpatrick     case KEY_RIGHT:
1270*f6aab3d8Srobert     case KEY_SF:
1271be691f3bSpatrick       MoveCursorRight();
1272be691f3bSpatrick       return eKeyHandled;
1273be691f3bSpatrick     case KEY_LEFT:
1274*f6aab3d8Srobert     case KEY_SR:
1275be691f3bSpatrick       MoveCursorLeft();
1276be691f3bSpatrick       return eKeyHandled;
1277be691f3bSpatrick     case KEY_BACKSPACE:
1278*f6aab3d8Srobert     case KEY_DELETE:
1279*f6aab3d8Srobert       RemovePreviousChar();
1280*f6aab3d8Srobert       return eKeyHandled;
1281*f6aab3d8Srobert     case KEY_DC:
1282*f6aab3d8Srobert       RemoveNextChar();
1283*f6aab3d8Srobert       return eKeyHandled;
1284*f6aab3d8Srobert     case KEY_EOL:
1285*f6aab3d8Srobert     case KEY_CTRL_K:
1286*f6aab3d8Srobert       ClearToEnd();
1287*f6aab3d8Srobert       return eKeyHandled;
1288*f6aab3d8Srobert     case KEY_DL:
1289*f6aab3d8Srobert     case KEY_CLEAR:
1290*f6aab3d8Srobert       Clear();
1291be691f3bSpatrick       return eKeyHandled;
1292be691f3bSpatrick     default:
1293be691f3bSpatrick       break;
1294be691f3bSpatrick     }
1295be691f3bSpatrick     return eKeyNotHandled;
1296be691f3bSpatrick   }
1297be691f3bSpatrick 
FieldDelegateHasError()1298be691f3bSpatrick   bool FieldDelegateHasError() override { return !m_error.empty(); }
1299be691f3bSpatrick 
FieldDelegateExitCallback()1300be691f3bSpatrick   void FieldDelegateExitCallback() override {
1301be691f3bSpatrick     if (!IsSpecified() && m_required)
1302be691f3bSpatrick       SetError("This field is required!");
1303be691f3bSpatrick   }
1304be691f3bSpatrick 
IsSpecified()1305be691f3bSpatrick   bool IsSpecified() { return !m_content.empty(); }
1306be691f3bSpatrick 
ClearError()1307be691f3bSpatrick   void ClearError() { m_error.clear(); }
1308be691f3bSpatrick 
GetError()1309be691f3bSpatrick   const std::string &GetError() { return m_error; }
1310be691f3bSpatrick 
SetError(const char * error)1311be691f3bSpatrick   void SetError(const char *error) { m_error = error; }
1312be691f3bSpatrick 
GetText()1313be691f3bSpatrick   const std::string &GetText() { return m_content; }
1314be691f3bSpatrick 
SetText(const char * text)1315*f6aab3d8Srobert   void SetText(const char *text) {
1316*f6aab3d8Srobert     if (text == nullptr) {
1317*f6aab3d8Srobert       m_content.clear();
1318*f6aab3d8Srobert       return;
1319*f6aab3d8Srobert     }
1320*f6aab3d8Srobert     m_content = text;
1321*f6aab3d8Srobert   }
1322*f6aab3d8Srobert 
1323be691f3bSpatrick protected:
1324be691f3bSpatrick   std::string m_label;
1325be691f3bSpatrick   bool m_required;
1326be691f3bSpatrick   // The position of the top left corner character of the border.
1327be691f3bSpatrick   std::string m_content;
1328be691f3bSpatrick   // The cursor position in the content string itself. Can be in the range
1329be691f3bSpatrick   // [0, GetContentLength()].
1330*f6aab3d8Srobert   int m_cursor_position = 0;
1331be691f3bSpatrick   // The index of the first visible character in the content.
1332*f6aab3d8Srobert   int m_first_visibile_char = 0;
1333be691f3bSpatrick   // Optional error message. If empty, field is considered to have no error.
1334be691f3bSpatrick   std::string m_error;
1335be691f3bSpatrick };
1336be691f3bSpatrick 
1337be691f3bSpatrick class IntegerFieldDelegate : public TextFieldDelegate {
1338be691f3bSpatrick public:
IntegerFieldDelegate(const char * label,int content,bool required)1339be691f3bSpatrick   IntegerFieldDelegate(const char *label, int content, bool required)
1340be691f3bSpatrick       : TextFieldDelegate(label, std::to_string(content).c_str(), required) {}
1341be691f3bSpatrick 
1342be691f3bSpatrick   // Only accept digits.
IsAcceptableChar(int key)1343be691f3bSpatrick   bool IsAcceptableChar(int key) override { return isdigit(key); }
1344be691f3bSpatrick 
1345be691f3bSpatrick   // Returns the integer content of the field.
GetInteger()1346be691f3bSpatrick   int GetInteger() { return std::stoi(m_content); }
1347be691f3bSpatrick };
1348be691f3bSpatrick 
1349be691f3bSpatrick class FileFieldDelegate : public TextFieldDelegate {
1350be691f3bSpatrick public:
FileFieldDelegate(const char * label,const char * content,bool need_to_exist,bool required)1351be691f3bSpatrick   FileFieldDelegate(const char *label, const char *content, bool need_to_exist,
1352be691f3bSpatrick                     bool required)
1353be691f3bSpatrick       : TextFieldDelegate(label, content, required),
1354be691f3bSpatrick         m_need_to_exist(need_to_exist) {}
1355be691f3bSpatrick 
FieldDelegateExitCallback()1356be691f3bSpatrick   void FieldDelegateExitCallback() override {
1357be691f3bSpatrick     TextFieldDelegate::FieldDelegateExitCallback();
1358be691f3bSpatrick     if (!IsSpecified())
1359be691f3bSpatrick       return;
1360be691f3bSpatrick 
1361be691f3bSpatrick     if (!m_need_to_exist)
1362be691f3bSpatrick       return;
1363be691f3bSpatrick 
1364be691f3bSpatrick     FileSpec file = GetResolvedFileSpec();
1365be691f3bSpatrick     if (!FileSystem::Instance().Exists(file)) {
1366be691f3bSpatrick       SetError("File doesn't exist!");
1367be691f3bSpatrick       return;
1368be691f3bSpatrick     }
1369be691f3bSpatrick     if (FileSystem::Instance().IsDirectory(file)) {
1370be691f3bSpatrick       SetError("Not a file!");
1371be691f3bSpatrick       return;
1372be691f3bSpatrick     }
1373be691f3bSpatrick   }
1374be691f3bSpatrick 
GetFileSpec()1375be691f3bSpatrick   FileSpec GetFileSpec() {
1376be691f3bSpatrick     FileSpec file_spec(GetPath());
1377be691f3bSpatrick     return file_spec;
1378be691f3bSpatrick   }
1379be691f3bSpatrick 
GetResolvedFileSpec()1380be691f3bSpatrick   FileSpec GetResolvedFileSpec() {
1381be691f3bSpatrick     FileSpec file_spec(GetPath());
1382be691f3bSpatrick     FileSystem::Instance().Resolve(file_spec);
1383be691f3bSpatrick     return file_spec;
1384be691f3bSpatrick   }
1385be691f3bSpatrick 
GetPath()1386be691f3bSpatrick   const std::string &GetPath() { return m_content; }
1387be691f3bSpatrick 
1388be691f3bSpatrick protected:
1389be691f3bSpatrick   bool m_need_to_exist;
1390be691f3bSpatrick };
1391be691f3bSpatrick 
1392be691f3bSpatrick class DirectoryFieldDelegate : public TextFieldDelegate {
1393be691f3bSpatrick public:
DirectoryFieldDelegate(const char * label,const char * content,bool need_to_exist,bool required)1394be691f3bSpatrick   DirectoryFieldDelegate(const char *label, const char *content,
1395be691f3bSpatrick                          bool need_to_exist, bool required)
1396be691f3bSpatrick       : TextFieldDelegate(label, content, required),
1397be691f3bSpatrick         m_need_to_exist(need_to_exist) {}
1398be691f3bSpatrick 
FieldDelegateExitCallback()1399be691f3bSpatrick   void FieldDelegateExitCallback() override {
1400be691f3bSpatrick     TextFieldDelegate::FieldDelegateExitCallback();
1401be691f3bSpatrick     if (!IsSpecified())
1402be691f3bSpatrick       return;
1403be691f3bSpatrick 
1404be691f3bSpatrick     if (!m_need_to_exist)
1405be691f3bSpatrick       return;
1406be691f3bSpatrick 
1407be691f3bSpatrick     FileSpec file = GetResolvedFileSpec();
1408be691f3bSpatrick     if (!FileSystem::Instance().Exists(file)) {
1409be691f3bSpatrick       SetError("Directory doesn't exist!");
1410be691f3bSpatrick       return;
1411be691f3bSpatrick     }
1412be691f3bSpatrick     if (!FileSystem::Instance().IsDirectory(file)) {
1413be691f3bSpatrick       SetError("Not a directory!");
1414be691f3bSpatrick       return;
1415be691f3bSpatrick     }
1416be691f3bSpatrick   }
1417be691f3bSpatrick 
GetFileSpec()1418be691f3bSpatrick   FileSpec GetFileSpec() {
1419be691f3bSpatrick     FileSpec file_spec(GetPath());
1420be691f3bSpatrick     return file_spec;
1421be691f3bSpatrick   }
1422be691f3bSpatrick 
GetResolvedFileSpec()1423be691f3bSpatrick   FileSpec GetResolvedFileSpec() {
1424be691f3bSpatrick     FileSpec file_spec(GetPath());
1425be691f3bSpatrick     FileSystem::Instance().Resolve(file_spec);
1426be691f3bSpatrick     return file_spec;
1427be691f3bSpatrick   }
1428be691f3bSpatrick 
GetPath()1429be691f3bSpatrick   const std::string &GetPath() { return m_content; }
1430be691f3bSpatrick 
1431be691f3bSpatrick protected:
1432be691f3bSpatrick   bool m_need_to_exist;
1433be691f3bSpatrick };
1434be691f3bSpatrick 
1435be691f3bSpatrick class ArchFieldDelegate : public TextFieldDelegate {
1436be691f3bSpatrick public:
ArchFieldDelegate(const char * label,const char * content,bool required)1437be691f3bSpatrick   ArchFieldDelegate(const char *label, const char *content, bool required)
1438be691f3bSpatrick       : TextFieldDelegate(label, content, required) {}
1439be691f3bSpatrick 
FieldDelegateExitCallback()1440be691f3bSpatrick   void FieldDelegateExitCallback() override {
1441be691f3bSpatrick     TextFieldDelegate::FieldDelegateExitCallback();
1442be691f3bSpatrick     if (!IsSpecified())
1443be691f3bSpatrick       return;
1444be691f3bSpatrick 
1445be691f3bSpatrick     if (!GetArchSpec().IsValid())
1446be691f3bSpatrick       SetError("Not a valid arch!");
1447be691f3bSpatrick   }
1448be691f3bSpatrick 
GetArchString()1449be691f3bSpatrick   const std::string &GetArchString() { return m_content; }
1450be691f3bSpatrick 
GetArchSpec()1451be691f3bSpatrick   ArchSpec GetArchSpec() { return ArchSpec(GetArchString()); }
1452be691f3bSpatrick };
1453be691f3bSpatrick 
1454be691f3bSpatrick class BooleanFieldDelegate : public FieldDelegate {
1455be691f3bSpatrick public:
BooleanFieldDelegate(const char * label,bool content)1456be691f3bSpatrick   BooleanFieldDelegate(const char *label, bool content)
1457be691f3bSpatrick       : m_label(label), m_content(content) {}
1458be691f3bSpatrick 
1459be691f3bSpatrick   // Boolean fields are drawn as checkboxes.
1460be691f3bSpatrick   //
1461be691f3bSpatrick   // [X] Label  or [ ] Label
1462be691f3bSpatrick 
1463be691f3bSpatrick   // Boolean fields are have a single line.
FieldDelegateGetHeight()1464be691f3bSpatrick   int FieldDelegateGetHeight() override { return 1; }
1465be691f3bSpatrick 
FieldDelegateDraw(Surface & surface,bool is_selected)1466*f6aab3d8Srobert   void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1467be691f3bSpatrick     surface.MoveCursor(0, 0);
1468be691f3bSpatrick     surface.PutChar('[');
1469be691f3bSpatrick     if (is_selected)
1470be691f3bSpatrick       surface.AttributeOn(A_REVERSE);
1471be691f3bSpatrick     surface.PutChar(m_content ? ACS_DIAMOND : ' ');
1472be691f3bSpatrick     if (is_selected)
1473be691f3bSpatrick       surface.AttributeOff(A_REVERSE);
1474be691f3bSpatrick     surface.PutChar(']');
1475be691f3bSpatrick     surface.PutChar(' ');
1476be691f3bSpatrick     surface.PutCString(m_label.c_str());
1477be691f3bSpatrick   }
1478be691f3bSpatrick 
ToggleContent()1479be691f3bSpatrick   void ToggleContent() { m_content = !m_content; }
1480be691f3bSpatrick 
SetContentToTrue()1481be691f3bSpatrick   void SetContentToTrue() { m_content = true; }
1482be691f3bSpatrick 
SetContentToFalse()1483be691f3bSpatrick   void SetContentToFalse() { m_content = false; }
1484be691f3bSpatrick 
FieldDelegateHandleChar(int key)1485be691f3bSpatrick   HandleCharResult FieldDelegateHandleChar(int key) override {
1486be691f3bSpatrick     switch (key) {
1487be691f3bSpatrick     case 't':
1488be691f3bSpatrick     case '1':
1489be691f3bSpatrick       SetContentToTrue();
1490be691f3bSpatrick       return eKeyHandled;
1491be691f3bSpatrick     case 'f':
1492be691f3bSpatrick     case '0':
1493be691f3bSpatrick       SetContentToFalse();
1494be691f3bSpatrick       return eKeyHandled;
1495be691f3bSpatrick     case ' ':
1496be691f3bSpatrick     case '\r':
1497be691f3bSpatrick     case '\n':
1498be691f3bSpatrick     case KEY_ENTER:
1499be691f3bSpatrick       ToggleContent();
1500be691f3bSpatrick       return eKeyHandled;
1501be691f3bSpatrick     default:
1502be691f3bSpatrick       break;
1503be691f3bSpatrick     }
1504be691f3bSpatrick     return eKeyNotHandled;
1505be691f3bSpatrick   }
1506be691f3bSpatrick 
1507be691f3bSpatrick   // Returns the boolean content of the field.
GetBoolean()1508be691f3bSpatrick   bool GetBoolean() { return m_content; }
1509be691f3bSpatrick 
1510be691f3bSpatrick protected:
1511be691f3bSpatrick   std::string m_label;
1512be691f3bSpatrick   bool m_content;
1513be691f3bSpatrick };
1514be691f3bSpatrick 
1515be691f3bSpatrick class ChoicesFieldDelegate : public FieldDelegate {
1516be691f3bSpatrick public:
ChoicesFieldDelegate(const char * label,int number_of_visible_choices,std::vector<std::string> choices)1517be691f3bSpatrick   ChoicesFieldDelegate(const char *label, int number_of_visible_choices,
1518be691f3bSpatrick                        std::vector<std::string> choices)
1519be691f3bSpatrick       : m_label(label), m_number_of_visible_choices(number_of_visible_choices),
1520*f6aab3d8Srobert         m_choices(choices) {}
1521be691f3bSpatrick 
1522be691f3bSpatrick   // Choices fields are drawn as titles boxses of a number of visible choices.
1523be691f3bSpatrick   // The rest of the choices become visible as the user scroll. The selected
1524be691f3bSpatrick   // choice is denoted by a diamond as the first character.
1525be691f3bSpatrick   //
1526be691f3bSpatrick   // __[Label]___________
1527be691f3bSpatrick   // |-Choice 1         |
1528be691f3bSpatrick   // | Choice 2         |
1529be691f3bSpatrick   // | Choice 3         |
1530be691f3bSpatrick   // |__________________|
1531be691f3bSpatrick 
1532be691f3bSpatrick   // Choices field have two border characters plus the number of visible
1533be691f3bSpatrick   // choices.
FieldDelegateGetHeight()1534be691f3bSpatrick   int FieldDelegateGetHeight() override {
1535be691f3bSpatrick     return m_number_of_visible_choices + 2;
1536be691f3bSpatrick   }
1537be691f3bSpatrick 
GetNumberOfChoices()1538be691f3bSpatrick   int GetNumberOfChoices() { return m_choices.size(); }
1539be691f3bSpatrick 
1540be691f3bSpatrick   // Get the index of the last visible choice.
GetLastVisibleChoice()1541be691f3bSpatrick   int GetLastVisibleChoice() {
1542be691f3bSpatrick     int index = m_first_visibile_choice + m_number_of_visible_choices;
1543be691f3bSpatrick     return std::min(index, GetNumberOfChoices()) - 1;
1544be691f3bSpatrick   }
1545be691f3bSpatrick 
DrawContent(Surface & surface,bool is_selected)1546*f6aab3d8Srobert   void DrawContent(Surface &surface, bool is_selected) {
1547be691f3bSpatrick     int choices_to_draw = GetLastVisibleChoice() - m_first_visibile_choice + 1;
1548be691f3bSpatrick     for (int i = 0; i < choices_to_draw; i++) {
1549be691f3bSpatrick       surface.MoveCursor(0, i);
1550be691f3bSpatrick       int current_choice = m_first_visibile_choice + i;
1551be691f3bSpatrick       const char *text = m_choices[current_choice].c_str();
1552be691f3bSpatrick       bool highlight = is_selected && current_choice == m_choice;
1553be691f3bSpatrick       if (highlight)
1554be691f3bSpatrick         surface.AttributeOn(A_REVERSE);
1555be691f3bSpatrick       surface.PutChar(current_choice == m_choice ? ACS_DIAMOND : ' ');
1556be691f3bSpatrick       surface.PutCString(text);
1557be691f3bSpatrick       if (highlight)
1558be691f3bSpatrick         surface.AttributeOff(A_REVERSE);
1559be691f3bSpatrick     }
1560be691f3bSpatrick   }
1561be691f3bSpatrick 
FieldDelegateDraw(Surface & surface,bool is_selected)1562*f6aab3d8Srobert   void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1563be691f3bSpatrick     UpdateScrolling();
1564be691f3bSpatrick 
1565be691f3bSpatrick     surface.TitledBox(m_label.c_str());
1566be691f3bSpatrick 
1567be691f3bSpatrick     Rect content_bounds = surface.GetFrame();
1568be691f3bSpatrick     content_bounds.Inset(1, 1);
1569*f6aab3d8Srobert     Surface content_surface = surface.SubSurface(content_bounds);
1570be691f3bSpatrick 
1571be691f3bSpatrick     DrawContent(content_surface, is_selected);
1572be691f3bSpatrick   }
1573be691f3bSpatrick 
SelectPrevious()1574be691f3bSpatrick   void SelectPrevious() {
1575be691f3bSpatrick     if (m_choice > 0)
1576be691f3bSpatrick       m_choice--;
1577be691f3bSpatrick   }
1578be691f3bSpatrick 
SelectNext()1579be691f3bSpatrick   void SelectNext() {
1580be691f3bSpatrick     if (m_choice < GetNumberOfChoices() - 1)
1581be691f3bSpatrick       m_choice++;
1582be691f3bSpatrick   }
1583be691f3bSpatrick 
UpdateScrolling()1584be691f3bSpatrick   void UpdateScrolling() {
1585be691f3bSpatrick     if (m_choice > GetLastVisibleChoice()) {
1586be691f3bSpatrick       m_first_visibile_choice = m_choice - (m_number_of_visible_choices - 1);
1587be691f3bSpatrick       return;
1588be691f3bSpatrick     }
1589be691f3bSpatrick 
1590be691f3bSpatrick     if (m_choice < m_first_visibile_choice)
1591be691f3bSpatrick       m_first_visibile_choice = m_choice;
1592be691f3bSpatrick   }
1593be691f3bSpatrick 
FieldDelegateHandleChar(int key)1594be691f3bSpatrick   HandleCharResult FieldDelegateHandleChar(int key) override {
1595be691f3bSpatrick     switch (key) {
1596be691f3bSpatrick     case KEY_UP:
1597be691f3bSpatrick       SelectPrevious();
1598be691f3bSpatrick       return eKeyHandled;
1599be691f3bSpatrick     case KEY_DOWN:
1600be691f3bSpatrick       SelectNext();
1601be691f3bSpatrick       return eKeyHandled;
1602be691f3bSpatrick     default:
1603be691f3bSpatrick       break;
1604be691f3bSpatrick     }
1605be691f3bSpatrick     return eKeyNotHandled;
1606be691f3bSpatrick   }
1607be691f3bSpatrick 
1608be691f3bSpatrick   // Returns the content of the choice as a string.
GetChoiceContent()1609be691f3bSpatrick   std::string GetChoiceContent() { return m_choices[m_choice]; }
1610be691f3bSpatrick 
1611be691f3bSpatrick   // Returns the index of the choice.
GetChoice()1612be691f3bSpatrick   int GetChoice() { return m_choice; }
1613be691f3bSpatrick 
SetChoice(llvm::StringRef choice)1614*f6aab3d8Srobert   void SetChoice(llvm::StringRef choice) {
1615be691f3bSpatrick     for (int i = 0; i < GetNumberOfChoices(); i++) {
1616be691f3bSpatrick       if (choice == m_choices[i]) {
1617be691f3bSpatrick         m_choice = i;
1618be691f3bSpatrick         return;
1619be691f3bSpatrick       }
1620be691f3bSpatrick     }
1621be691f3bSpatrick   }
1622be691f3bSpatrick 
1623be691f3bSpatrick protected:
1624be691f3bSpatrick   std::string m_label;
1625be691f3bSpatrick   int m_number_of_visible_choices;
1626be691f3bSpatrick   std::vector<std::string> m_choices;
1627be691f3bSpatrick   // The index of the selected choice.
1628*f6aab3d8Srobert   int m_choice = 0;
1629be691f3bSpatrick   // The index of the first visible choice in the field.
1630*f6aab3d8Srobert   int m_first_visibile_choice = 0;
1631be691f3bSpatrick };
1632be691f3bSpatrick 
1633be691f3bSpatrick class PlatformPluginFieldDelegate : public ChoicesFieldDelegate {
1634be691f3bSpatrick public:
PlatformPluginFieldDelegate(Debugger & debugger)1635be691f3bSpatrick   PlatformPluginFieldDelegate(Debugger &debugger)
1636be691f3bSpatrick       : ChoicesFieldDelegate("Platform Plugin", 3, GetPossiblePluginNames()) {
1637be691f3bSpatrick     PlatformSP platform_sp = debugger.GetPlatformList().GetSelectedPlatform();
1638be691f3bSpatrick     if (platform_sp)
1639*f6aab3d8Srobert       SetChoice(platform_sp->GetPluginName());
1640be691f3bSpatrick   }
1641be691f3bSpatrick 
GetPossiblePluginNames()1642be691f3bSpatrick   std::vector<std::string> GetPossiblePluginNames() {
1643be691f3bSpatrick     std::vector<std::string> names;
1644be691f3bSpatrick     size_t i = 0;
1645*f6aab3d8Srobert     for (llvm::StringRef name =
1646*f6aab3d8Srobert              PluginManager::GetPlatformPluginNameAtIndex(i++);
1647*f6aab3d8Srobert          !name.empty(); name = PluginManager::GetProcessPluginNameAtIndex(i++))
1648*f6aab3d8Srobert       names.push_back(name.str());
1649be691f3bSpatrick     return names;
1650be691f3bSpatrick   }
1651be691f3bSpatrick 
GetPluginName()1652be691f3bSpatrick   std::string GetPluginName() {
1653be691f3bSpatrick     std::string plugin_name = GetChoiceContent();
1654be691f3bSpatrick     return plugin_name;
1655be691f3bSpatrick   }
1656be691f3bSpatrick };
1657be691f3bSpatrick 
1658be691f3bSpatrick class ProcessPluginFieldDelegate : public ChoicesFieldDelegate {
1659be691f3bSpatrick public:
ProcessPluginFieldDelegate()1660be691f3bSpatrick   ProcessPluginFieldDelegate()
1661be691f3bSpatrick       : ChoicesFieldDelegate("Process Plugin", 3, GetPossiblePluginNames()) {}
1662be691f3bSpatrick 
GetPossiblePluginNames()1663be691f3bSpatrick   std::vector<std::string> GetPossiblePluginNames() {
1664be691f3bSpatrick     std::vector<std::string> names;
1665be691f3bSpatrick     names.push_back("<default>");
1666be691f3bSpatrick 
1667be691f3bSpatrick     size_t i = 0;
1668*f6aab3d8Srobert     for (llvm::StringRef name = PluginManager::GetProcessPluginNameAtIndex(i++);
1669*f6aab3d8Srobert          !name.empty(); name = PluginManager::GetProcessPluginNameAtIndex(i++))
1670*f6aab3d8Srobert       names.push_back(name.str());
1671be691f3bSpatrick     return names;
1672be691f3bSpatrick   }
1673be691f3bSpatrick 
GetPluginName()1674be691f3bSpatrick   std::string GetPluginName() {
1675be691f3bSpatrick     std::string plugin_name = GetChoiceContent();
1676be691f3bSpatrick     if (plugin_name == "<default>")
1677be691f3bSpatrick       return "";
1678be691f3bSpatrick     return plugin_name;
1679be691f3bSpatrick   }
1680be691f3bSpatrick };
1681be691f3bSpatrick 
1682*f6aab3d8Srobert class LazyBooleanFieldDelegate : public ChoicesFieldDelegate {
1683*f6aab3d8Srobert public:
LazyBooleanFieldDelegate(const char * label,const char * calculate_label)1684*f6aab3d8Srobert   LazyBooleanFieldDelegate(const char *label, const char *calculate_label)
1685*f6aab3d8Srobert       : ChoicesFieldDelegate(label, 3, GetPossibleOptions(calculate_label)) {}
1686*f6aab3d8Srobert 
1687*f6aab3d8Srobert   static constexpr const char *kNo = "No";
1688*f6aab3d8Srobert   static constexpr const char *kYes = "Yes";
1689*f6aab3d8Srobert 
GetPossibleOptions(const char * calculate_label)1690*f6aab3d8Srobert   std::vector<std::string> GetPossibleOptions(const char *calculate_label) {
1691*f6aab3d8Srobert     std::vector<std::string> options;
1692*f6aab3d8Srobert     options.push_back(calculate_label);
1693*f6aab3d8Srobert     options.push_back(kYes);
1694*f6aab3d8Srobert     options.push_back(kNo);
1695*f6aab3d8Srobert     return options;
1696*f6aab3d8Srobert   }
1697*f6aab3d8Srobert 
GetLazyBoolean()1698*f6aab3d8Srobert   LazyBool GetLazyBoolean() {
1699*f6aab3d8Srobert     std::string choice = GetChoiceContent();
1700*f6aab3d8Srobert     if (choice == kNo)
1701*f6aab3d8Srobert       return eLazyBoolNo;
1702*f6aab3d8Srobert     else if (choice == kYes)
1703*f6aab3d8Srobert       return eLazyBoolYes;
1704*f6aab3d8Srobert     else
1705*f6aab3d8Srobert       return eLazyBoolCalculate;
1706*f6aab3d8Srobert   }
1707*f6aab3d8Srobert };
1708*f6aab3d8Srobert 
1709be691f3bSpatrick template <class T> class ListFieldDelegate : public FieldDelegate {
1710be691f3bSpatrick public:
ListFieldDelegate(const char * label,T default_field)1711be691f3bSpatrick   ListFieldDelegate(const char *label, T default_field)
1712*f6aab3d8Srobert       : m_label(label), m_default_field(default_field),
1713be691f3bSpatrick         m_selection_type(SelectionType::NewButton) {}
1714be691f3bSpatrick 
1715be691f3bSpatrick   // Signify which element is selected. If a field or a remove button is
1716be691f3bSpatrick   // selected, then m_selection_index signifies the particular field that
1717be691f3bSpatrick   // is selected or the field that the remove button belongs to.
1718be691f3bSpatrick   enum class SelectionType { Field, RemoveButton, NewButton };
1719be691f3bSpatrick 
1720be691f3bSpatrick   // A List field is drawn as a titled box of a number of other fields of the
1721be691f3bSpatrick   // same type. Each field has a Remove button next to it that removes the
1722be691f3bSpatrick   // corresponding field. Finally, the last line contains a New button to add a
1723be691f3bSpatrick   // new field.
1724be691f3bSpatrick   //
1725be691f3bSpatrick   // __[Label]___________
1726be691f3bSpatrick   // | Field 0 [Remove] |
1727be691f3bSpatrick   // | Field 1 [Remove] |
1728be691f3bSpatrick   // | Field 2 [Remove] |
1729be691f3bSpatrick   // |      [New]       |
1730be691f3bSpatrick   // |__________________|
1731be691f3bSpatrick 
1732be691f3bSpatrick   // List fields have two lines for border characters, 1 line for the New
1733be691f3bSpatrick   // button, and the total height of the available fields.
FieldDelegateGetHeight()1734be691f3bSpatrick   int FieldDelegateGetHeight() override {
1735be691f3bSpatrick     // 2 border characters.
1736be691f3bSpatrick     int height = 2;
1737be691f3bSpatrick     // Total height of the fields.
1738be691f3bSpatrick     for (int i = 0; i < GetNumberOfFields(); i++) {
1739be691f3bSpatrick       height += m_fields[i].FieldDelegateGetHeight();
1740be691f3bSpatrick     }
1741be691f3bSpatrick     // A line for the New button.
1742be691f3bSpatrick     height++;
1743be691f3bSpatrick     return height;
1744be691f3bSpatrick   }
1745be691f3bSpatrick 
FieldDelegateGetScrollContext()1746be691f3bSpatrick   ScrollContext FieldDelegateGetScrollContext() override {
1747be691f3bSpatrick     int height = FieldDelegateGetHeight();
1748be691f3bSpatrick     if (m_selection_type == SelectionType::NewButton)
1749be691f3bSpatrick       return ScrollContext(height - 2, height - 1);
1750be691f3bSpatrick 
1751be691f3bSpatrick     FieldDelegate &field = m_fields[m_selection_index];
1752be691f3bSpatrick     ScrollContext context = field.FieldDelegateGetScrollContext();
1753be691f3bSpatrick 
1754be691f3bSpatrick     // Start at 1 because of the top border.
1755be691f3bSpatrick     int offset = 1;
1756be691f3bSpatrick     for (int i = 0; i < m_selection_index; i++) {
1757be691f3bSpatrick       offset += m_fields[i].FieldDelegateGetHeight();
1758be691f3bSpatrick     }
1759be691f3bSpatrick     context.Offset(offset);
1760be691f3bSpatrick 
1761be691f3bSpatrick     // If the scroll context is touching the top border, include it in the
1762be691f3bSpatrick     // context to show the label.
1763be691f3bSpatrick     if (context.start == 1)
1764be691f3bSpatrick       context.start--;
1765be691f3bSpatrick 
1766be691f3bSpatrick     // If the scroll context is touching the new button, include it as well as
1767be691f3bSpatrick     // the bottom border in the context.
1768be691f3bSpatrick     if (context.end == height - 3)
1769be691f3bSpatrick       context.end += 2;
1770be691f3bSpatrick 
1771be691f3bSpatrick     return context;
1772be691f3bSpatrick   }
1773be691f3bSpatrick 
DrawRemoveButton(Surface & surface,int highlight)1774*f6aab3d8Srobert   void DrawRemoveButton(Surface &surface, int highlight) {
1775be691f3bSpatrick     surface.MoveCursor(1, surface.GetHeight() / 2);
1776be691f3bSpatrick     if (highlight)
1777be691f3bSpatrick       surface.AttributeOn(A_REVERSE);
1778be691f3bSpatrick     surface.PutCString("[Remove]");
1779be691f3bSpatrick     if (highlight)
1780be691f3bSpatrick       surface.AttributeOff(A_REVERSE);
1781be691f3bSpatrick   }
1782be691f3bSpatrick 
DrawFields(Surface & surface,bool is_selected)1783*f6aab3d8Srobert   void DrawFields(Surface &surface, bool is_selected) {
1784be691f3bSpatrick     int line = 0;
1785be691f3bSpatrick     int width = surface.GetWidth();
1786be691f3bSpatrick     for (int i = 0; i < GetNumberOfFields(); i++) {
1787be691f3bSpatrick       int height = m_fields[i].FieldDelegateGetHeight();
1788be691f3bSpatrick       Rect bounds = Rect(Point(0, line), Size(width, height));
1789be691f3bSpatrick       Rect field_bounds, remove_button_bounds;
1790be691f3bSpatrick       bounds.VerticalSplit(bounds.size.width - sizeof(" [Remove]"),
1791be691f3bSpatrick                            field_bounds, remove_button_bounds);
1792*f6aab3d8Srobert       Surface field_surface = surface.SubSurface(field_bounds);
1793*f6aab3d8Srobert       Surface remove_button_surface = surface.SubSurface(remove_button_bounds);
1794be691f3bSpatrick 
1795be691f3bSpatrick       bool is_element_selected = m_selection_index == i && is_selected;
1796be691f3bSpatrick       bool is_field_selected =
1797be691f3bSpatrick           is_element_selected && m_selection_type == SelectionType::Field;
1798be691f3bSpatrick       bool is_remove_button_selected =
1799be691f3bSpatrick           is_element_selected &&
1800be691f3bSpatrick           m_selection_type == SelectionType::RemoveButton;
1801be691f3bSpatrick       m_fields[i].FieldDelegateDraw(field_surface, is_field_selected);
1802be691f3bSpatrick       DrawRemoveButton(remove_button_surface, is_remove_button_selected);
1803be691f3bSpatrick 
1804be691f3bSpatrick       line += height;
1805be691f3bSpatrick     }
1806be691f3bSpatrick   }
1807be691f3bSpatrick 
DrawNewButton(Surface & surface,bool is_selected)1808*f6aab3d8Srobert   void DrawNewButton(Surface &surface, bool is_selected) {
1809be691f3bSpatrick     const char *button_text = "[New]";
1810be691f3bSpatrick     int x = (surface.GetWidth() - sizeof(button_text) - 1) / 2;
1811be691f3bSpatrick     surface.MoveCursor(x, 0);
1812be691f3bSpatrick     bool highlight =
1813be691f3bSpatrick         is_selected && m_selection_type == SelectionType::NewButton;
1814be691f3bSpatrick     if (highlight)
1815be691f3bSpatrick       surface.AttributeOn(A_REVERSE);
1816be691f3bSpatrick     surface.PutCString(button_text);
1817be691f3bSpatrick     if (highlight)
1818be691f3bSpatrick       surface.AttributeOff(A_REVERSE);
1819be691f3bSpatrick   }
1820be691f3bSpatrick 
FieldDelegateDraw(Surface & surface,bool is_selected)1821*f6aab3d8Srobert   void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1822be691f3bSpatrick     surface.TitledBox(m_label.c_str());
1823be691f3bSpatrick 
1824be691f3bSpatrick     Rect content_bounds = surface.GetFrame();
1825be691f3bSpatrick     content_bounds.Inset(1, 1);
1826be691f3bSpatrick     Rect fields_bounds, new_button_bounds;
1827be691f3bSpatrick     content_bounds.HorizontalSplit(content_bounds.size.height - 1,
1828be691f3bSpatrick                                    fields_bounds, new_button_bounds);
1829*f6aab3d8Srobert     Surface fields_surface = surface.SubSurface(fields_bounds);
1830*f6aab3d8Srobert     Surface new_button_surface = surface.SubSurface(new_button_bounds);
1831be691f3bSpatrick 
1832be691f3bSpatrick     DrawFields(fields_surface, is_selected);
1833be691f3bSpatrick     DrawNewButton(new_button_surface, is_selected);
1834be691f3bSpatrick   }
1835be691f3bSpatrick 
AddNewField()1836be691f3bSpatrick   void AddNewField() {
1837be691f3bSpatrick     m_fields.push_back(m_default_field);
1838be691f3bSpatrick     m_selection_index = GetNumberOfFields() - 1;
1839be691f3bSpatrick     m_selection_type = SelectionType::Field;
1840be691f3bSpatrick     FieldDelegate &field = m_fields[m_selection_index];
1841be691f3bSpatrick     field.FieldDelegateSelectFirstElement();
1842be691f3bSpatrick   }
1843be691f3bSpatrick 
RemoveField()1844be691f3bSpatrick   void RemoveField() {
1845be691f3bSpatrick     m_fields.erase(m_fields.begin() + m_selection_index);
1846be691f3bSpatrick     if (m_selection_index != 0)
1847be691f3bSpatrick       m_selection_index--;
1848be691f3bSpatrick 
1849be691f3bSpatrick     if (GetNumberOfFields() > 0) {
1850be691f3bSpatrick       m_selection_type = SelectionType::Field;
1851be691f3bSpatrick       FieldDelegate &field = m_fields[m_selection_index];
1852be691f3bSpatrick       field.FieldDelegateSelectFirstElement();
1853be691f3bSpatrick     } else
1854be691f3bSpatrick       m_selection_type = SelectionType::NewButton;
1855be691f3bSpatrick   }
1856be691f3bSpatrick 
SelectNext(int key)1857be691f3bSpatrick   HandleCharResult SelectNext(int key) {
1858be691f3bSpatrick     if (m_selection_type == SelectionType::NewButton)
1859be691f3bSpatrick       return eKeyNotHandled;
1860be691f3bSpatrick 
1861be691f3bSpatrick     if (m_selection_type == SelectionType::RemoveButton) {
1862be691f3bSpatrick       if (m_selection_index == GetNumberOfFields() - 1) {
1863be691f3bSpatrick         m_selection_type = SelectionType::NewButton;
1864be691f3bSpatrick         return eKeyHandled;
1865be691f3bSpatrick       }
1866be691f3bSpatrick       m_selection_index++;
1867be691f3bSpatrick       m_selection_type = SelectionType::Field;
1868be691f3bSpatrick       FieldDelegate &next_field = m_fields[m_selection_index];
1869be691f3bSpatrick       next_field.FieldDelegateSelectFirstElement();
1870be691f3bSpatrick       return eKeyHandled;
1871be691f3bSpatrick     }
1872be691f3bSpatrick 
1873be691f3bSpatrick     FieldDelegate &field = m_fields[m_selection_index];
1874be691f3bSpatrick     if (!field.FieldDelegateOnLastOrOnlyElement()) {
1875be691f3bSpatrick       return field.FieldDelegateHandleChar(key);
1876be691f3bSpatrick     }
1877be691f3bSpatrick 
1878be691f3bSpatrick     field.FieldDelegateExitCallback();
1879be691f3bSpatrick 
1880be691f3bSpatrick     m_selection_type = SelectionType::RemoveButton;
1881be691f3bSpatrick     return eKeyHandled;
1882be691f3bSpatrick   }
1883be691f3bSpatrick 
SelectPrevious(int key)1884be691f3bSpatrick   HandleCharResult SelectPrevious(int key) {
1885be691f3bSpatrick     if (FieldDelegateOnFirstOrOnlyElement())
1886be691f3bSpatrick       return eKeyNotHandled;
1887be691f3bSpatrick 
1888be691f3bSpatrick     if (m_selection_type == SelectionType::RemoveButton) {
1889be691f3bSpatrick       m_selection_type = SelectionType::Field;
1890be691f3bSpatrick       FieldDelegate &field = m_fields[m_selection_index];
1891be691f3bSpatrick       field.FieldDelegateSelectLastElement();
1892be691f3bSpatrick       return eKeyHandled;
1893be691f3bSpatrick     }
1894be691f3bSpatrick 
1895be691f3bSpatrick     if (m_selection_type == SelectionType::NewButton) {
1896be691f3bSpatrick       m_selection_type = SelectionType::RemoveButton;
1897be691f3bSpatrick       m_selection_index = GetNumberOfFields() - 1;
1898be691f3bSpatrick       return eKeyHandled;
1899be691f3bSpatrick     }
1900be691f3bSpatrick 
1901be691f3bSpatrick     FieldDelegate &field = m_fields[m_selection_index];
1902be691f3bSpatrick     if (!field.FieldDelegateOnFirstOrOnlyElement()) {
1903be691f3bSpatrick       return field.FieldDelegateHandleChar(key);
1904be691f3bSpatrick     }
1905be691f3bSpatrick 
1906be691f3bSpatrick     field.FieldDelegateExitCallback();
1907be691f3bSpatrick 
1908be691f3bSpatrick     m_selection_type = SelectionType::RemoveButton;
1909be691f3bSpatrick     m_selection_index--;
1910be691f3bSpatrick     return eKeyHandled;
1911be691f3bSpatrick   }
1912be691f3bSpatrick 
1913*f6aab3d8Srobert   // If the last element of the field is selected and it didn't handle the key.
1914*f6aab3d8Srobert   // Select the next field or new button if the selected field is the last one.
SelectNextInList(int key)1915*f6aab3d8Srobert   HandleCharResult SelectNextInList(int key) {
1916*f6aab3d8Srobert     assert(m_selection_type == SelectionType::Field);
1917*f6aab3d8Srobert 
1918*f6aab3d8Srobert     FieldDelegate &field = m_fields[m_selection_index];
1919*f6aab3d8Srobert     if (field.FieldDelegateHandleChar(key) == eKeyHandled)
1920*f6aab3d8Srobert       return eKeyHandled;
1921*f6aab3d8Srobert 
1922*f6aab3d8Srobert     if (!field.FieldDelegateOnLastOrOnlyElement())
1923*f6aab3d8Srobert       return eKeyNotHandled;
1924*f6aab3d8Srobert 
1925*f6aab3d8Srobert     field.FieldDelegateExitCallback();
1926*f6aab3d8Srobert 
1927*f6aab3d8Srobert     if (m_selection_index == GetNumberOfFields() - 1) {
1928*f6aab3d8Srobert       m_selection_type = SelectionType::NewButton;
1929*f6aab3d8Srobert       return eKeyHandled;
1930*f6aab3d8Srobert     }
1931*f6aab3d8Srobert 
1932*f6aab3d8Srobert     m_selection_index++;
1933*f6aab3d8Srobert     FieldDelegate &next_field = m_fields[m_selection_index];
1934*f6aab3d8Srobert     next_field.FieldDelegateSelectFirstElement();
1935*f6aab3d8Srobert     return eKeyHandled;
1936*f6aab3d8Srobert   }
1937*f6aab3d8Srobert 
FieldDelegateHandleChar(int key)1938be691f3bSpatrick   HandleCharResult FieldDelegateHandleChar(int key) override {
1939be691f3bSpatrick     switch (key) {
1940be691f3bSpatrick     case '\r':
1941be691f3bSpatrick     case '\n':
1942be691f3bSpatrick     case KEY_ENTER:
1943be691f3bSpatrick       switch (m_selection_type) {
1944be691f3bSpatrick       case SelectionType::NewButton:
1945be691f3bSpatrick         AddNewField();
1946be691f3bSpatrick         return eKeyHandled;
1947be691f3bSpatrick       case SelectionType::RemoveButton:
1948be691f3bSpatrick         RemoveField();
1949be691f3bSpatrick         return eKeyHandled;
1950*f6aab3d8Srobert       case SelectionType::Field:
1951*f6aab3d8Srobert         return SelectNextInList(key);
1952be691f3bSpatrick       }
1953be691f3bSpatrick       break;
1954be691f3bSpatrick     case '\t':
1955*f6aab3d8Srobert       return SelectNext(key);
1956be691f3bSpatrick     case KEY_SHIFT_TAB:
1957*f6aab3d8Srobert       return SelectPrevious(key);
1958be691f3bSpatrick     default:
1959be691f3bSpatrick       break;
1960be691f3bSpatrick     }
1961be691f3bSpatrick 
1962be691f3bSpatrick     // If the key wasn't handled and one of the fields is selected, pass the key
1963be691f3bSpatrick     // to that field.
1964be691f3bSpatrick     if (m_selection_type == SelectionType::Field) {
1965be691f3bSpatrick       return m_fields[m_selection_index].FieldDelegateHandleChar(key);
1966be691f3bSpatrick     }
1967be691f3bSpatrick 
1968be691f3bSpatrick     return eKeyNotHandled;
1969be691f3bSpatrick   }
1970be691f3bSpatrick 
FieldDelegateOnLastOrOnlyElement()1971be691f3bSpatrick   bool FieldDelegateOnLastOrOnlyElement() override {
1972be691f3bSpatrick     if (m_selection_type == SelectionType::NewButton) {
1973be691f3bSpatrick       return true;
1974be691f3bSpatrick     }
1975be691f3bSpatrick     return false;
1976be691f3bSpatrick   }
1977be691f3bSpatrick 
FieldDelegateOnFirstOrOnlyElement()1978be691f3bSpatrick   bool FieldDelegateOnFirstOrOnlyElement() override {
1979be691f3bSpatrick     if (m_selection_type == SelectionType::NewButton &&
1980be691f3bSpatrick         GetNumberOfFields() == 0)
1981be691f3bSpatrick       return true;
1982be691f3bSpatrick 
1983be691f3bSpatrick     if (m_selection_type == SelectionType::Field && m_selection_index == 0) {
1984be691f3bSpatrick       FieldDelegate &field = m_fields[m_selection_index];
1985be691f3bSpatrick       return field.FieldDelegateOnFirstOrOnlyElement();
1986be691f3bSpatrick     }
1987be691f3bSpatrick 
1988be691f3bSpatrick     return false;
1989be691f3bSpatrick   }
1990be691f3bSpatrick 
FieldDelegateSelectFirstElement()1991be691f3bSpatrick   void FieldDelegateSelectFirstElement() override {
1992be691f3bSpatrick     if (GetNumberOfFields() == 0) {
1993be691f3bSpatrick       m_selection_type = SelectionType::NewButton;
1994be691f3bSpatrick       return;
1995be691f3bSpatrick     }
1996be691f3bSpatrick 
1997be691f3bSpatrick     m_selection_type = SelectionType::Field;
1998be691f3bSpatrick     m_selection_index = 0;
1999be691f3bSpatrick   }
2000be691f3bSpatrick 
FieldDelegateSelectLastElement()2001be691f3bSpatrick   void FieldDelegateSelectLastElement() override {
2002be691f3bSpatrick     m_selection_type = SelectionType::NewButton;
2003be691f3bSpatrick   }
2004be691f3bSpatrick 
GetNumberOfFields()2005be691f3bSpatrick   int GetNumberOfFields() { return m_fields.size(); }
2006be691f3bSpatrick 
2007be691f3bSpatrick   // Returns the form delegate at the current index.
GetField(int index)2008be691f3bSpatrick   T &GetField(int index) { return m_fields[index]; }
2009be691f3bSpatrick 
2010be691f3bSpatrick protected:
2011be691f3bSpatrick   std::string m_label;
2012be691f3bSpatrick   // The default field delegate instance from which new field delegates will be
2013be691f3bSpatrick   // created though a copy.
2014be691f3bSpatrick   T m_default_field;
2015be691f3bSpatrick   std::vector<T> m_fields;
2016*f6aab3d8Srobert   int m_selection_index = 0;
2017be691f3bSpatrick   // See SelectionType class enum.
2018be691f3bSpatrick   SelectionType m_selection_type;
2019be691f3bSpatrick };
2020be691f3bSpatrick 
2021*f6aab3d8Srobert class ArgumentsFieldDelegate : public ListFieldDelegate<TextFieldDelegate> {
2022*f6aab3d8Srobert public:
ArgumentsFieldDelegate()2023*f6aab3d8Srobert   ArgumentsFieldDelegate()
2024*f6aab3d8Srobert       : ListFieldDelegate("Arguments",
2025*f6aab3d8Srobert                           TextFieldDelegate("Argument", "", false)) {}
2026*f6aab3d8Srobert 
GetArguments()2027*f6aab3d8Srobert   Args GetArguments() {
2028*f6aab3d8Srobert     Args arguments;
2029*f6aab3d8Srobert     for (int i = 0; i < GetNumberOfFields(); i++) {
2030*f6aab3d8Srobert       arguments.AppendArgument(GetField(i).GetText());
2031*f6aab3d8Srobert     }
2032*f6aab3d8Srobert     return arguments;
2033*f6aab3d8Srobert   }
2034*f6aab3d8Srobert 
AddArguments(const Args & arguments)2035*f6aab3d8Srobert   void AddArguments(const Args &arguments) {
2036*f6aab3d8Srobert     for (size_t i = 0; i < arguments.GetArgumentCount(); i++) {
2037*f6aab3d8Srobert       AddNewField();
2038*f6aab3d8Srobert       TextFieldDelegate &field = GetField(GetNumberOfFields() - 1);
2039*f6aab3d8Srobert       field.SetText(arguments.GetArgumentAtIndex(i));
2040*f6aab3d8Srobert     }
2041*f6aab3d8Srobert   }
2042*f6aab3d8Srobert };
2043*f6aab3d8Srobert 
2044*f6aab3d8Srobert template <class KeyFieldDelegateType, class ValueFieldDelegateType>
2045*f6aab3d8Srobert class MappingFieldDelegate : public FieldDelegate {
2046*f6aab3d8Srobert public:
MappingFieldDelegate(KeyFieldDelegateType key_field,ValueFieldDelegateType value_field)2047*f6aab3d8Srobert   MappingFieldDelegate(KeyFieldDelegateType key_field,
2048*f6aab3d8Srobert                        ValueFieldDelegateType value_field)
2049*f6aab3d8Srobert       : m_key_field(key_field), m_value_field(value_field),
2050*f6aab3d8Srobert         m_selection_type(SelectionType::Key) {}
2051*f6aab3d8Srobert 
2052*f6aab3d8Srobert   // Signify which element is selected. The key field or its value field.
2053*f6aab3d8Srobert   enum class SelectionType { Key, Value };
2054*f6aab3d8Srobert 
2055*f6aab3d8Srobert   // A mapping field is drawn as two text fields with a right arrow in between.
2056*f6aab3d8Srobert   // The first field stores the key of the mapping and the second stores the
2057*f6aab3d8Srobert   // value if the mapping.
2058*f6aab3d8Srobert   //
2059*f6aab3d8Srobert   // __[Key]_____________   __[Value]___________
2060*f6aab3d8Srobert   // |                  | > |                  |
2061*f6aab3d8Srobert   // |__________________|   |__________________|
2062*f6aab3d8Srobert   // - Error message if it exists.
2063*f6aab3d8Srobert 
2064*f6aab3d8Srobert   // The mapping field has a height that is equal to the maximum height between
2065*f6aab3d8Srobert   // the key and value fields.
FieldDelegateGetHeight()2066*f6aab3d8Srobert   int FieldDelegateGetHeight() override {
2067*f6aab3d8Srobert     return std::max(m_key_field.FieldDelegateGetHeight(),
2068*f6aab3d8Srobert                     m_value_field.FieldDelegateGetHeight());
2069*f6aab3d8Srobert   }
2070*f6aab3d8Srobert 
DrawArrow(Surface & surface)2071*f6aab3d8Srobert   void DrawArrow(Surface &surface) {
2072*f6aab3d8Srobert     surface.MoveCursor(0, 1);
2073*f6aab3d8Srobert     surface.PutChar(ACS_RARROW);
2074*f6aab3d8Srobert   }
2075*f6aab3d8Srobert 
FieldDelegateDraw(Surface & surface,bool is_selected)2076*f6aab3d8Srobert   void FieldDelegateDraw(Surface &surface, bool is_selected) override {
2077*f6aab3d8Srobert     Rect bounds = surface.GetFrame();
2078*f6aab3d8Srobert     Rect key_field_bounds, arrow_and_value_field_bounds;
2079*f6aab3d8Srobert     bounds.VerticalSplit(bounds.size.width / 2, key_field_bounds,
2080*f6aab3d8Srobert                          arrow_and_value_field_bounds);
2081*f6aab3d8Srobert     Rect arrow_bounds, value_field_bounds;
2082*f6aab3d8Srobert     arrow_and_value_field_bounds.VerticalSplit(1, arrow_bounds,
2083*f6aab3d8Srobert                                                value_field_bounds);
2084*f6aab3d8Srobert 
2085*f6aab3d8Srobert     Surface key_field_surface = surface.SubSurface(key_field_bounds);
2086*f6aab3d8Srobert     Surface arrow_surface = surface.SubSurface(arrow_bounds);
2087*f6aab3d8Srobert     Surface value_field_surface = surface.SubSurface(value_field_bounds);
2088*f6aab3d8Srobert 
2089*f6aab3d8Srobert     bool key_is_selected =
2090*f6aab3d8Srobert         m_selection_type == SelectionType::Key && is_selected;
2091*f6aab3d8Srobert     m_key_field.FieldDelegateDraw(key_field_surface, key_is_selected);
2092*f6aab3d8Srobert     DrawArrow(arrow_surface);
2093*f6aab3d8Srobert     bool value_is_selected =
2094*f6aab3d8Srobert         m_selection_type == SelectionType::Value && is_selected;
2095*f6aab3d8Srobert     m_value_field.FieldDelegateDraw(value_field_surface, value_is_selected);
2096*f6aab3d8Srobert   }
2097*f6aab3d8Srobert 
SelectNext(int key)2098*f6aab3d8Srobert   HandleCharResult SelectNext(int key) {
2099*f6aab3d8Srobert     if (FieldDelegateOnLastOrOnlyElement())
2100*f6aab3d8Srobert       return eKeyNotHandled;
2101*f6aab3d8Srobert 
2102*f6aab3d8Srobert     if (!m_key_field.FieldDelegateOnLastOrOnlyElement()) {
2103*f6aab3d8Srobert       return m_key_field.FieldDelegateHandleChar(key);
2104*f6aab3d8Srobert     }
2105*f6aab3d8Srobert 
2106*f6aab3d8Srobert     m_key_field.FieldDelegateExitCallback();
2107*f6aab3d8Srobert     m_selection_type = SelectionType::Value;
2108*f6aab3d8Srobert     m_value_field.FieldDelegateSelectFirstElement();
2109*f6aab3d8Srobert     return eKeyHandled;
2110*f6aab3d8Srobert   }
2111*f6aab3d8Srobert 
SelectPrevious(int key)2112*f6aab3d8Srobert   HandleCharResult SelectPrevious(int key) {
2113*f6aab3d8Srobert     if (FieldDelegateOnFirstOrOnlyElement())
2114*f6aab3d8Srobert       return eKeyNotHandled;
2115*f6aab3d8Srobert 
2116*f6aab3d8Srobert     if (!m_value_field.FieldDelegateOnFirstOrOnlyElement()) {
2117*f6aab3d8Srobert       return m_value_field.FieldDelegateHandleChar(key);
2118*f6aab3d8Srobert     }
2119*f6aab3d8Srobert 
2120*f6aab3d8Srobert     m_value_field.FieldDelegateExitCallback();
2121*f6aab3d8Srobert     m_selection_type = SelectionType::Key;
2122*f6aab3d8Srobert     m_key_field.FieldDelegateSelectLastElement();
2123*f6aab3d8Srobert     return eKeyHandled;
2124*f6aab3d8Srobert   }
2125*f6aab3d8Srobert 
2126*f6aab3d8Srobert   // If the value field is selected, pass the key to it. If the key field is
2127*f6aab3d8Srobert   // selected, its last element is selected, and it didn't handle the key, then
2128*f6aab3d8Srobert   // select its corresponding value field.
SelectNextField(int key)2129*f6aab3d8Srobert   HandleCharResult SelectNextField(int key) {
2130*f6aab3d8Srobert     if (m_selection_type == SelectionType::Value) {
2131*f6aab3d8Srobert       return m_value_field.FieldDelegateHandleChar(key);
2132*f6aab3d8Srobert     }
2133*f6aab3d8Srobert 
2134*f6aab3d8Srobert     if (m_key_field.FieldDelegateHandleChar(key) == eKeyHandled)
2135*f6aab3d8Srobert       return eKeyHandled;
2136*f6aab3d8Srobert 
2137*f6aab3d8Srobert     if (!m_key_field.FieldDelegateOnLastOrOnlyElement())
2138*f6aab3d8Srobert       return eKeyNotHandled;
2139*f6aab3d8Srobert 
2140*f6aab3d8Srobert     m_key_field.FieldDelegateExitCallback();
2141*f6aab3d8Srobert     m_selection_type = SelectionType::Value;
2142*f6aab3d8Srobert     m_value_field.FieldDelegateSelectFirstElement();
2143*f6aab3d8Srobert     return eKeyHandled;
2144*f6aab3d8Srobert   }
2145*f6aab3d8Srobert 
FieldDelegateHandleChar(int key)2146*f6aab3d8Srobert   HandleCharResult FieldDelegateHandleChar(int key) override {
2147*f6aab3d8Srobert     switch (key) {
2148*f6aab3d8Srobert     case KEY_RETURN:
2149*f6aab3d8Srobert       return SelectNextField(key);
2150*f6aab3d8Srobert     case '\t':
2151*f6aab3d8Srobert       return SelectNext(key);
2152*f6aab3d8Srobert     case KEY_SHIFT_TAB:
2153*f6aab3d8Srobert       return SelectPrevious(key);
2154*f6aab3d8Srobert     default:
2155*f6aab3d8Srobert       break;
2156*f6aab3d8Srobert     }
2157*f6aab3d8Srobert 
2158*f6aab3d8Srobert     // If the key wasn't handled, pass the key to the selected field.
2159*f6aab3d8Srobert     if (m_selection_type == SelectionType::Key)
2160*f6aab3d8Srobert       return m_key_field.FieldDelegateHandleChar(key);
2161*f6aab3d8Srobert     else
2162*f6aab3d8Srobert       return m_value_field.FieldDelegateHandleChar(key);
2163*f6aab3d8Srobert 
2164*f6aab3d8Srobert     return eKeyNotHandled;
2165*f6aab3d8Srobert   }
2166*f6aab3d8Srobert 
FieldDelegateOnFirstOrOnlyElement()2167*f6aab3d8Srobert   bool FieldDelegateOnFirstOrOnlyElement() override {
2168*f6aab3d8Srobert     return m_selection_type == SelectionType::Key;
2169*f6aab3d8Srobert   }
2170*f6aab3d8Srobert 
FieldDelegateOnLastOrOnlyElement()2171*f6aab3d8Srobert   bool FieldDelegateOnLastOrOnlyElement() override {
2172*f6aab3d8Srobert     return m_selection_type == SelectionType::Value;
2173*f6aab3d8Srobert   }
2174*f6aab3d8Srobert 
FieldDelegateSelectFirstElement()2175*f6aab3d8Srobert   void FieldDelegateSelectFirstElement() override {
2176*f6aab3d8Srobert     m_selection_type = SelectionType::Key;
2177*f6aab3d8Srobert   }
2178*f6aab3d8Srobert 
FieldDelegateSelectLastElement()2179*f6aab3d8Srobert   void FieldDelegateSelectLastElement() override {
2180*f6aab3d8Srobert     m_selection_type = SelectionType::Value;
2181*f6aab3d8Srobert   }
2182*f6aab3d8Srobert 
FieldDelegateHasError()2183*f6aab3d8Srobert   bool FieldDelegateHasError() override {
2184*f6aab3d8Srobert     return m_key_field.FieldDelegateHasError() ||
2185*f6aab3d8Srobert            m_value_field.FieldDelegateHasError();
2186*f6aab3d8Srobert   }
2187*f6aab3d8Srobert 
GetKeyField()2188*f6aab3d8Srobert   KeyFieldDelegateType &GetKeyField() { return m_key_field; }
2189*f6aab3d8Srobert 
GetValueField()2190*f6aab3d8Srobert   ValueFieldDelegateType &GetValueField() { return m_value_field; }
2191*f6aab3d8Srobert 
2192*f6aab3d8Srobert protected:
2193*f6aab3d8Srobert   KeyFieldDelegateType m_key_field;
2194*f6aab3d8Srobert   ValueFieldDelegateType m_value_field;
2195*f6aab3d8Srobert   // See SelectionType class enum.
2196*f6aab3d8Srobert   SelectionType m_selection_type;
2197*f6aab3d8Srobert };
2198*f6aab3d8Srobert 
2199*f6aab3d8Srobert class EnvironmentVariableNameFieldDelegate : public TextFieldDelegate {
2200*f6aab3d8Srobert public:
EnvironmentVariableNameFieldDelegate(const char * content)2201*f6aab3d8Srobert   EnvironmentVariableNameFieldDelegate(const char *content)
2202*f6aab3d8Srobert       : TextFieldDelegate("Name", content, true) {}
2203*f6aab3d8Srobert 
2204*f6aab3d8Srobert   // Environment variable names can't contain an equal sign.
IsAcceptableChar(int key)2205*f6aab3d8Srobert   bool IsAcceptableChar(int key) override {
2206*f6aab3d8Srobert     return TextFieldDelegate::IsAcceptableChar(key) && key != '=';
2207*f6aab3d8Srobert   }
2208*f6aab3d8Srobert 
GetName()2209*f6aab3d8Srobert   const std::string &GetName() { return m_content; }
2210*f6aab3d8Srobert };
2211*f6aab3d8Srobert 
2212*f6aab3d8Srobert class EnvironmentVariableFieldDelegate
2213*f6aab3d8Srobert     : public MappingFieldDelegate<EnvironmentVariableNameFieldDelegate,
2214*f6aab3d8Srobert                                   TextFieldDelegate> {
2215*f6aab3d8Srobert public:
EnvironmentVariableFieldDelegate()2216*f6aab3d8Srobert   EnvironmentVariableFieldDelegate()
2217*f6aab3d8Srobert       : MappingFieldDelegate(
2218*f6aab3d8Srobert             EnvironmentVariableNameFieldDelegate(""),
2219*f6aab3d8Srobert             TextFieldDelegate("Value", "", /*required=*/false)) {}
2220*f6aab3d8Srobert 
GetName()2221*f6aab3d8Srobert   const std::string &GetName() { return GetKeyField().GetName(); }
2222*f6aab3d8Srobert 
GetValue()2223*f6aab3d8Srobert   const std::string &GetValue() { return GetValueField().GetText(); }
2224*f6aab3d8Srobert 
SetName(const char * name)2225*f6aab3d8Srobert   void SetName(const char *name) { return GetKeyField().SetText(name); }
2226*f6aab3d8Srobert 
SetValue(const char * value)2227*f6aab3d8Srobert   void SetValue(const char *value) { return GetValueField().SetText(value); }
2228*f6aab3d8Srobert };
2229*f6aab3d8Srobert 
2230*f6aab3d8Srobert class EnvironmentVariableListFieldDelegate
2231*f6aab3d8Srobert     : public ListFieldDelegate<EnvironmentVariableFieldDelegate> {
2232*f6aab3d8Srobert public:
EnvironmentVariableListFieldDelegate(const char * label)2233*f6aab3d8Srobert   EnvironmentVariableListFieldDelegate(const char *label)
2234*f6aab3d8Srobert       : ListFieldDelegate(label, EnvironmentVariableFieldDelegate()) {}
2235*f6aab3d8Srobert 
GetEnvironment()2236*f6aab3d8Srobert   Environment GetEnvironment() {
2237*f6aab3d8Srobert     Environment environment;
2238*f6aab3d8Srobert     for (int i = 0; i < GetNumberOfFields(); i++) {
2239*f6aab3d8Srobert       environment.insert(
2240*f6aab3d8Srobert           std::make_pair(GetField(i).GetName(), GetField(i).GetValue()));
2241*f6aab3d8Srobert     }
2242*f6aab3d8Srobert     return environment;
2243*f6aab3d8Srobert   }
2244*f6aab3d8Srobert 
AddEnvironmentVariables(const Environment & environment)2245*f6aab3d8Srobert   void AddEnvironmentVariables(const Environment &environment) {
2246*f6aab3d8Srobert     for (auto &variable : environment) {
2247*f6aab3d8Srobert       AddNewField();
2248*f6aab3d8Srobert       EnvironmentVariableFieldDelegate &field =
2249*f6aab3d8Srobert           GetField(GetNumberOfFields() - 1);
2250*f6aab3d8Srobert       field.SetName(variable.getKey().str().c_str());
2251*f6aab3d8Srobert       field.SetValue(variable.getValue().c_str());
2252*f6aab3d8Srobert     }
2253*f6aab3d8Srobert   }
2254*f6aab3d8Srobert };
2255*f6aab3d8Srobert 
2256be691f3bSpatrick class FormAction {
2257be691f3bSpatrick public:
FormAction(const char * label,std::function<void (Window &)> action)2258be691f3bSpatrick   FormAction(const char *label, std::function<void(Window &)> action)
2259be691f3bSpatrick       : m_action(action) {
2260be691f3bSpatrick     if (label)
2261be691f3bSpatrick       m_label = label;
2262be691f3bSpatrick   }
2263be691f3bSpatrick 
2264be691f3bSpatrick   // Draw a centered [Label].
Draw(Surface & surface,bool is_selected)2265*f6aab3d8Srobert   void Draw(Surface &surface, bool is_selected) {
2266be691f3bSpatrick     int x = (surface.GetWidth() - m_label.length()) / 2;
2267be691f3bSpatrick     surface.MoveCursor(x, 0);
2268be691f3bSpatrick     if (is_selected)
2269be691f3bSpatrick       surface.AttributeOn(A_REVERSE);
2270be691f3bSpatrick     surface.PutChar('[');
2271be691f3bSpatrick     surface.PutCString(m_label.c_str());
2272be691f3bSpatrick     surface.PutChar(']');
2273be691f3bSpatrick     if (is_selected)
2274be691f3bSpatrick       surface.AttributeOff(A_REVERSE);
2275be691f3bSpatrick   }
2276be691f3bSpatrick 
Execute(Window & window)2277be691f3bSpatrick   void Execute(Window &window) { m_action(window); }
2278be691f3bSpatrick 
GetLabel()2279be691f3bSpatrick   const std::string &GetLabel() { return m_label; }
2280be691f3bSpatrick 
2281be691f3bSpatrick protected:
2282be691f3bSpatrick   std::string m_label;
2283be691f3bSpatrick   std::function<void(Window &)> m_action;
2284be691f3bSpatrick };
2285be691f3bSpatrick 
2286be691f3bSpatrick class FormDelegate {
2287be691f3bSpatrick public:
2288*f6aab3d8Srobert   FormDelegate() = default;
2289be691f3bSpatrick 
2290be691f3bSpatrick   virtual ~FormDelegate() = default;
2291be691f3bSpatrick 
2292be691f3bSpatrick   virtual std::string GetName() = 0;
2293be691f3bSpatrick 
UpdateFieldsVisibility()2294*f6aab3d8Srobert   virtual void UpdateFieldsVisibility() {}
2295be691f3bSpatrick 
GetField(uint32_t field_index)2296be691f3bSpatrick   FieldDelegate *GetField(uint32_t field_index) {
2297be691f3bSpatrick     if (field_index < m_fields.size())
2298be691f3bSpatrick       return m_fields[field_index].get();
2299be691f3bSpatrick     return nullptr;
2300be691f3bSpatrick   }
2301be691f3bSpatrick 
GetAction(int action_index)2302be691f3bSpatrick   FormAction &GetAction(int action_index) { return m_actions[action_index]; }
2303be691f3bSpatrick 
GetNumberOfFields()2304be691f3bSpatrick   int GetNumberOfFields() { return m_fields.size(); }
2305be691f3bSpatrick 
GetNumberOfActions()2306be691f3bSpatrick   int GetNumberOfActions() { return m_actions.size(); }
2307be691f3bSpatrick 
HasError()2308be691f3bSpatrick   bool HasError() { return !m_error.empty(); }
2309be691f3bSpatrick 
ClearError()2310be691f3bSpatrick   void ClearError() { m_error.clear(); }
2311be691f3bSpatrick 
GetError()2312be691f3bSpatrick   const std::string &GetError() { return m_error; }
2313be691f3bSpatrick 
SetError(const char * error)2314be691f3bSpatrick   void SetError(const char *error) { m_error = error; }
2315be691f3bSpatrick 
2316be691f3bSpatrick   // If all fields are valid, true is returned. Otherwise, an error message is
2317be691f3bSpatrick   // set and false is returned. This method is usually called at the start of an
2318be691f3bSpatrick   // action that requires valid fields.
CheckFieldsValidity()2319be691f3bSpatrick   bool CheckFieldsValidity() {
2320be691f3bSpatrick     for (int i = 0; i < GetNumberOfFields(); i++) {
2321*f6aab3d8Srobert       GetField(i)->FieldDelegateExitCallback();
2322be691f3bSpatrick       if (GetField(i)->FieldDelegateHasError()) {
2323be691f3bSpatrick         SetError("Some fields are invalid!");
2324be691f3bSpatrick         return false;
2325be691f3bSpatrick       }
2326be691f3bSpatrick     }
2327be691f3bSpatrick     return true;
2328be691f3bSpatrick   }
2329be691f3bSpatrick 
2330be691f3bSpatrick   // Factory methods to create and add fields of specific types.
2331be691f3bSpatrick 
AddTextField(const char * label,const char * content,bool required)2332be691f3bSpatrick   TextFieldDelegate *AddTextField(const char *label, const char *content,
2333be691f3bSpatrick                                   bool required) {
2334be691f3bSpatrick     TextFieldDelegate *delegate =
2335be691f3bSpatrick         new TextFieldDelegate(label, content, required);
2336be691f3bSpatrick     m_fields.push_back(FieldDelegateUP(delegate));
2337be691f3bSpatrick     return delegate;
2338be691f3bSpatrick   }
2339be691f3bSpatrick 
AddFileField(const char * label,const char * content,bool need_to_exist,bool required)2340be691f3bSpatrick   FileFieldDelegate *AddFileField(const char *label, const char *content,
2341be691f3bSpatrick                                   bool need_to_exist, bool required) {
2342be691f3bSpatrick     FileFieldDelegate *delegate =
2343be691f3bSpatrick         new FileFieldDelegate(label, content, need_to_exist, required);
2344be691f3bSpatrick     m_fields.push_back(FieldDelegateUP(delegate));
2345be691f3bSpatrick     return delegate;
2346be691f3bSpatrick   }
2347be691f3bSpatrick 
AddDirectoryField(const char * label,const char * content,bool need_to_exist,bool required)2348be691f3bSpatrick   DirectoryFieldDelegate *AddDirectoryField(const char *label,
2349be691f3bSpatrick                                             const char *content,
2350be691f3bSpatrick                                             bool need_to_exist, bool required) {
2351be691f3bSpatrick     DirectoryFieldDelegate *delegate =
2352be691f3bSpatrick         new DirectoryFieldDelegate(label, content, need_to_exist, required);
2353be691f3bSpatrick     m_fields.push_back(FieldDelegateUP(delegate));
2354be691f3bSpatrick     return delegate;
2355be691f3bSpatrick   }
2356be691f3bSpatrick 
AddArchField(const char * label,const char * content,bool required)2357be691f3bSpatrick   ArchFieldDelegate *AddArchField(const char *label, const char *content,
2358be691f3bSpatrick                                   bool required) {
2359be691f3bSpatrick     ArchFieldDelegate *delegate =
2360be691f3bSpatrick         new ArchFieldDelegate(label, content, required);
2361be691f3bSpatrick     m_fields.push_back(FieldDelegateUP(delegate));
2362be691f3bSpatrick     return delegate;
2363be691f3bSpatrick   }
2364be691f3bSpatrick 
AddIntegerField(const char * label,int content,bool required)2365be691f3bSpatrick   IntegerFieldDelegate *AddIntegerField(const char *label, int content,
2366be691f3bSpatrick                                         bool required) {
2367be691f3bSpatrick     IntegerFieldDelegate *delegate =
2368be691f3bSpatrick         new IntegerFieldDelegate(label, content, required);
2369be691f3bSpatrick     m_fields.push_back(FieldDelegateUP(delegate));
2370be691f3bSpatrick     return delegate;
2371be691f3bSpatrick   }
2372be691f3bSpatrick 
AddBooleanField(const char * label,bool content)2373be691f3bSpatrick   BooleanFieldDelegate *AddBooleanField(const char *label, bool content) {
2374be691f3bSpatrick     BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content);
2375be691f3bSpatrick     m_fields.push_back(FieldDelegateUP(delegate));
2376be691f3bSpatrick     return delegate;
2377be691f3bSpatrick   }
2378be691f3bSpatrick 
AddLazyBooleanField(const char * label,const char * calculate_label)2379*f6aab3d8Srobert   LazyBooleanFieldDelegate *AddLazyBooleanField(const char *label,
2380*f6aab3d8Srobert                                                 const char *calculate_label) {
2381*f6aab3d8Srobert     LazyBooleanFieldDelegate *delegate =
2382*f6aab3d8Srobert         new LazyBooleanFieldDelegate(label, calculate_label);
2383*f6aab3d8Srobert     m_fields.push_back(FieldDelegateUP(delegate));
2384*f6aab3d8Srobert     return delegate;
2385*f6aab3d8Srobert   }
2386*f6aab3d8Srobert 
AddChoicesField(const char * label,int height,std::vector<std::string> choices)2387be691f3bSpatrick   ChoicesFieldDelegate *AddChoicesField(const char *label, int height,
2388be691f3bSpatrick                                         std::vector<std::string> choices) {
2389be691f3bSpatrick     ChoicesFieldDelegate *delegate =
2390be691f3bSpatrick         new ChoicesFieldDelegate(label, height, choices);
2391be691f3bSpatrick     m_fields.push_back(FieldDelegateUP(delegate));
2392be691f3bSpatrick     return delegate;
2393be691f3bSpatrick   }
2394be691f3bSpatrick 
AddPlatformPluginField(Debugger & debugger)2395be691f3bSpatrick   PlatformPluginFieldDelegate *AddPlatformPluginField(Debugger &debugger) {
2396be691f3bSpatrick     PlatformPluginFieldDelegate *delegate =
2397be691f3bSpatrick         new PlatformPluginFieldDelegate(debugger);
2398be691f3bSpatrick     m_fields.push_back(FieldDelegateUP(delegate));
2399be691f3bSpatrick     return delegate;
2400be691f3bSpatrick   }
2401be691f3bSpatrick 
AddProcessPluginField()2402be691f3bSpatrick   ProcessPluginFieldDelegate *AddProcessPluginField() {
2403be691f3bSpatrick     ProcessPluginFieldDelegate *delegate = new ProcessPluginFieldDelegate();
2404be691f3bSpatrick     m_fields.push_back(FieldDelegateUP(delegate));
2405be691f3bSpatrick     return delegate;
2406be691f3bSpatrick   }
2407be691f3bSpatrick 
2408be691f3bSpatrick   template <class T>
AddListField(const char * label,T default_field)2409be691f3bSpatrick   ListFieldDelegate<T> *AddListField(const char *label, T default_field) {
2410be691f3bSpatrick     ListFieldDelegate<T> *delegate =
2411be691f3bSpatrick         new ListFieldDelegate<T>(label, default_field);
2412be691f3bSpatrick     m_fields.push_back(FieldDelegateUP(delegate));
2413be691f3bSpatrick     return delegate;
2414be691f3bSpatrick   }
2415be691f3bSpatrick 
AddArgumentsField()2416*f6aab3d8Srobert   ArgumentsFieldDelegate *AddArgumentsField() {
2417*f6aab3d8Srobert     ArgumentsFieldDelegate *delegate = new ArgumentsFieldDelegate();
2418*f6aab3d8Srobert     m_fields.push_back(FieldDelegateUP(delegate));
2419*f6aab3d8Srobert     return delegate;
2420*f6aab3d8Srobert   }
2421*f6aab3d8Srobert 
2422*f6aab3d8Srobert   template <class K, class V>
AddMappingField(K key_field,V value_field)2423*f6aab3d8Srobert   MappingFieldDelegate<K, V> *AddMappingField(K key_field, V value_field) {
2424*f6aab3d8Srobert     MappingFieldDelegate<K, V> *delegate =
2425*f6aab3d8Srobert         new MappingFieldDelegate<K, V>(key_field, value_field);
2426*f6aab3d8Srobert     m_fields.push_back(FieldDelegateUP(delegate));
2427*f6aab3d8Srobert     return delegate;
2428*f6aab3d8Srobert   }
2429*f6aab3d8Srobert 
2430*f6aab3d8Srobert   EnvironmentVariableNameFieldDelegate *
AddEnvironmentVariableNameField(const char * content)2431*f6aab3d8Srobert   AddEnvironmentVariableNameField(const char *content) {
2432*f6aab3d8Srobert     EnvironmentVariableNameFieldDelegate *delegate =
2433*f6aab3d8Srobert         new EnvironmentVariableNameFieldDelegate(content);
2434*f6aab3d8Srobert     m_fields.push_back(FieldDelegateUP(delegate));
2435*f6aab3d8Srobert     return delegate;
2436*f6aab3d8Srobert   }
2437*f6aab3d8Srobert 
AddEnvironmentVariableField()2438*f6aab3d8Srobert   EnvironmentVariableFieldDelegate *AddEnvironmentVariableField() {
2439*f6aab3d8Srobert     EnvironmentVariableFieldDelegate *delegate =
2440*f6aab3d8Srobert         new EnvironmentVariableFieldDelegate();
2441*f6aab3d8Srobert     m_fields.push_back(FieldDelegateUP(delegate));
2442*f6aab3d8Srobert     return delegate;
2443*f6aab3d8Srobert   }
2444*f6aab3d8Srobert 
2445*f6aab3d8Srobert   EnvironmentVariableListFieldDelegate *
AddEnvironmentVariableListField(const char * label)2446*f6aab3d8Srobert   AddEnvironmentVariableListField(const char *label) {
2447*f6aab3d8Srobert     EnvironmentVariableListFieldDelegate *delegate =
2448*f6aab3d8Srobert         new EnvironmentVariableListFieldDelegate(label);
2449*f6aab3d8Srobert     m_fields.push_back(FieldDelegateUP(delegate));
2450*f6aab3d8Srobert     return delegate;
2451*f6aab3d8Srobert   }
2452*f6aab3d8Srobert 
2453be691f3bSpatrick   // Factory methods for adding actions.
2454be691f3bSpatrick 
AddAction(const char * label,std::function<void (Window &)> action)2455be691f3bSpatrick   void AddAction(const char *label, std::function<void(Window &)> action) {
2456be691f3bSpatrick     m_actions.push_back(FormAction(label, action));
2457be691f3bSpatrick   }
2458be691f3bSpatrick 
2459be691f3bSpatrick protected:
2460be691f3bSpatrick   std::vector<FieldDelegateUP> m_fields;
2461be691f3bSpatrick   std::vector<FormAction> m_actions;
2462be691f3bSpatrick   // Optional error message. If empty, form is considered to have no error.
2463be691f3bSpatrick   std::string m_error;
2464be691f3bSpatrick };
2465be691f3bSpatrick 
2466be691f3bSpatrick typedef std::shared_ptr<FormDelegate> FormDelegateSP;
2467be691f3bSpatrick 
2468be691f3bSpatrick class FormWindowDelegate : public WindowDelegate {
2469be691f3bSpatrick public:
FormWindowDelegate(FormDelegateSP & delegate_sp)2470*f6aab3d8Srobert   FormWindowDelegate(FormDelegateSP &delegate_sp) : m_delegate_sp(delegate_sp) {
2471be691f3bSpatrick     assert(m_delegate_sp->GetNumberOfActions() > 0);
2472be691f3bSpatrick     if (m_delegate_sp->GetNumberOfFields() > 0)
2473be691f3bSpatrick       m_selection_type = SelectionType::Field;
2474be691f3bSpatrick     else
2475be691f3bSpatrick       m_selection_type = SelectionType::Action;
2476be691f3bSpatrick   }
2477be691f3bSpatrick 
2478be691f3bSpatrick   // Signify which element is selected. If a field or an action is selected,
2479be691f3bSpatrick   // then m_selection_index signifies the particular field or action that is
2480be691f3bSpatrick   // selected.
2481be691f3bSpatrick   enum class SelectionType { Field, Action };
2482be691f3bSpatrick 
2483be691f3bSpatrick   // A form window is padded by one character from all sides. First, if an error
2484be691f3bSpatrick   // message exists, it is drawn followed by a separator. Then one or more
2485be691f3bSpatrick   // fields are drawn. Finally, all available actions are drawn on a single
2486be691f3bSpatrick   // line.
2487be691f3bSpatrick   //
2488be691f3bSpatrick   // ___<Form Name>_________________________________________________
2489be691f3bSpatrick   // |                                                             |
2490be691f3bSpatrick   // | - Error message if it exists.                               |
2491be691f3bSpatrick   // |-------------------------------------------------------------|
2492be691f3bSpatrick   // | Form elements here.                                         |
2493be691f3bSpatrick   // |                       Form actions here.                    |
2494be691f3bSpatrick   // |                                                             |
2495be691f3bSpatrick   // |______________________________________[Press Esc to cancel]__|
2496be691f3bSpatrick   //
2497be691f3bSpatrick 
2498be691f3bSpatrick   // One line for the error and another for the horizontal line.
GetErrorHeight()2499be691f3bSpatrick   int GetErrorHeight() {
2500be691f3bSpatrick     if (m_delegate_sp->HasError())
2501be691f3bSpatrick       return 2;
2502be691f3bSpatrick     return 0;
2503be691f3bSpatrick   }
2504be691f3bSpatrick 
2505be691f3bSpatrick   // Actions span a single line.
GetActionsHeight()2506be691f3bSpatrick   int GetActionsHeight() {
2507be691f3bSpatrick     if (m_delegate_sp->GetNumberOfActions() > 0)
2508be691f3bSpatrick       return 1;
2509be691f3bSpatrick     return 0;
2510be691f3bSpatrick   }
2511be691f3bSpatrick 
2512be691f3bSpatrick   // Get the total number of needed lines to draw the contents.
GetContentHeight()2513be691f3bSpatrick   int GetContentHeight() {
2514be691f3bSpatrick     int height = 0;
2515be691f3bSpatrick     height += GetErrorHeight();
2516be691f3bSpatrick     for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2517be691f3bSpatrick       if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2518be691f3bSpatrick         continue;
2519be691f3bSpatrick       height += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2520be691f3bSpatrick     }
2521be691f3bSpatrick     height += GetActionsHeight();
2522be691f3bSpatrick     return height;
2523be691f3bSpatrick   }
2524be691f3bSpatrick 
GetScrollContext()2525be691f3bSpatrick   ScrollContext GetScrollContext() {
2526be691f3bSpatrick     if (m_selection_type == SelectionType::Action)
2527be691f3bSpatrick       return ScrollContext(GetContentHeight() - 1);
2528be691f3bSpatrick 
2529be691f3bSpatrick     FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2530be691f3bSpatrick     ScrollContext context = field->FieldDelegateGetScrollContext();
2531be691f3bSpatrick 
2532be691f3bSpatrick     int offset = GetErrorHeight();
2533be691f3bSpatrick     for (int i = 0; i < m_selection_index; i++) {
2534be691f3bSpatrick       if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2535be691f3bSpatrick         continue;
2536be691f3bSpatrick       offset += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2537be691f3bSpatrick     }
2538be691f3bSpatrick     context.Offset(offset);
2539be691f3bSpatrick 
2540be691f3bSpatrick     // If the context is touching the error, include the error in the context as
2541be691f3bSpatrick     // well.
2542be691f3bSpatrick     if (context.start == GetErrorHeight())
2543be691f3bSpatrick       context.start = 0;
2544be691f3bSpatrick 
2545be691f3bSpatrick     return context;
2546be691f3bSpatrick   }
2547be691f3bSpatrick 
UpdateScrolling(Surface & surface)2548*f6aab3d8Srobert   void UpdateScrolling(Surface &surface) {
2549be691f3bSpatrick     ScrollContext context = GetScrollContext();
2550be691f3bSpatrick     int content_height = GetContentHeight();
2551be691f3bSpatrick     int surface_height = surface.GetHeight();
2552be691f3bSpatrick     int visible_height = std::min(content_height, surface_height);
2553be691f3bSpatrick     int last_visible_line = m_first_visible_line + visible_height - 1;
2554be691f3bSpatrick 
2555be691f3bSpatrick     // If the last visible line is bigger than the content, then it is invalid
2556be691f3bSpatrick     // and needs to be set to the last line in the content. This can happen when
2557be691f3bSpatrick     // a field has shrunk in height.
2558be691f3bSpatrick     if (last_visible_line > content_height - 1) {
2559be691f3bSpatrick       m_first_visible_line = content_height - visible_height;
2560be691f3bSpatrick     }
2561be691f3bSpatrick 
2562be691f3bSpatrick     if (context.start < m_first_visible_line) {
2563be691f3bSpatrick       m_first_visible_line = context.start;
2564be691f3bSpatrick       return;
2565be691f3bSpatrick     }
2566be691f3bSpatrick 
2567be691f3bSpatrick     if (context.end > last_visible_line) {
2568be691f3bSpatrick       m_first_visible_line = context.end - visible_height + 1;
2569be691f3bSpatrick     }
2570be691f3bSpatrick   }
2571be691f3bSpatrick 
DrawError(Surface & surface)2572*f6aab3d8Srobert   void DrawError(Surface &surface) {
2573be691f3bSpatrick     if (!m_delegate_sp->HasError())
2574be691f3bSpatrick       return;
2575be691f3bSpatrick     surface.MoveCursor(0, 0);
2576be691f3bSpatrick     surface.AttributeOn(COLOR_PAIR(RedOnBlack));
2577be691f3bSpatrick     surface.PutChar(ACS_DIAMOND);
2578be691f3bSpatrick     surface.PutChar(' ');
2579be691f3bSpatrick     surface.PutCStringTruncated(1, m_delegate_sp->GetError().c_str());
2580be691f3bSpatrick     surface.AttributeOff(COLOR_PAIR(RedOnBlack));
2581be691f3bSpatrick 
2582be691f3bSpatrick     surface.MoveCursor(0, 1);
2583be691f3bSpatrick     surface.HorizontalLine(surface.GetWidth());
2584be691f3bSpatrick   }
2585be691f3bSpatrick 
DrawFields(Surface & surface)2586*f6aab3d8Srobert   void DrawFields(Surface &surface) {
2587be691f3bSpatrick     int line = 0;
2588be691f3bSpatrick     int width = surface.GetWidth();
2589be691f3bSpatrick     bool a_field_is_selected = m_selection_type == SelectionType::Field;
2590be691f3bSpatrick     for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2591be691f3bSpatrick       FieldDelegate *field = m_delegate_sp->GetField(i);
2592be691f3bSpatrick       if (!field->FieldDelegateIsVisible())
2593be691f3bSpatrick         continue;
2594be691f3bSpatrick       bool is_field_selected = a_field_is_selected && m_selection_index == i;
2595be691f3bSpatrick       int height = field->FieldDelegateGetHeight();
2596be691f3bSpatrick       Rect bounds = Rect(Point(0, line), Size(width, height));
2597*f6aab3d8Srobert       Surface field_surface = surface.SubSurface(bounds);
2598be691f3bSpatrick       field->FieldDelegateDraw(field_surface, is_field_selected);
2599be691f3bSpatrick       line += height;
2600be691f3bSpatrick     }
2601be691f3bSpatrick   }
2602be691f3bSpatrick 
DrawActions(Surface & surface)2603*f6aab3d8Srobert   void DrawActions(Surface &surface) {
2604be691f3bSpatrick     int number_of_actions = m_delegate_sp->GetNumberOfActions();
2605be691f3bSpatrick     int width = surface.GetWidth() / number_of_actions;
2606be691f3bSpatrick     bool an_action_is_selected = m_selection_type == SelectionType::Action;
2607be691f3bSpatrick     int x = 0;
2608be691f3bSpatrick     for (int i = 0; i < number_of_actions; i++) {
2609be691f3bSpatrick       bool is_action_selected = an_action_is_selected && m_selection_index == i;
2610be691f3bSpatrick       FormAction &action = m_delegate_sp->GetAction(i);
2611be691f3bSpatrick       Rect bounds = Rect(Point(x, 0), Size(width, 1));
2612*f6aab3d8Srobert       Surface action_surface = surface.SubSurface(bounds);
2613be691f3bSpatrick       action.Draw(action_surface, is_action_selected);
2614be691f3bSpatrick       x += width;
2615be691f3bSpatrick     }
2616be691f3bSpatrick   }
2617be691f3bSpatrick 
DrawElements(Surface & surface)2618*f6aab3d8Srobert   void DrawElements(Surface &surface) {
2619be691f3bSpatrick     Rect frame = surface.GetFrame();
2620be691f3bSpatrick     Rect fields_bounds, actions_bounds;
2621be691f3bSpatrick     frame.HorizontalSplit(surface.GetHeight() - GetActionsHeight(),
2622be691f3bSpatrick                           fields_bounds, actions_bounds);
2623*f6aab3d8Srobert     Surface fields_surface = surface.SubSurface(fields_bounds);
2624*f6aab3d8Srobert     Surface actions_surface = surface.SubSurface(actions_bounds);
2625be691f3bSpatrick 
2626be691f3bSpatrick     DrawFields(fields_surface);
2627be691f3bSpatrick     DrawActions(actions_surface);
2628be691f3bSpatrick   }
2629be691f3bSpatrick 
2630be691f3bSpatrick   // Contents are first drawn on a pad. Then a subset of that pad is copied to
2631be691f3bSpatrick   // the derived window starting at the first visible line. This essentially
2632be691f3bSpatrick   // provides scrolling functionality.
DrawContent(Surface & surface)2633*f6aab3d8Srobert   void DrawContent(Surface &surface) {
2634be691f3bSpatrick     UpdateScrolling(surface);
2635be691f3bSpatrick 
2636be691f3bSpatrick     int width = surface.GetWidth();
2637be691f3bSpatrick     int height = GetContentHeight();
2638be691f3bSpatrick     Pad pad = Pad(Size(width, height));
2639be691f3bSpatrick 
2640be691f3bSpatrick     Rect frame = pad.GetFrame();
2641be691f3bSpatrick     Rect error_bounds, elements_bounds;
2642be691f3bSpatrick     frame.HorizontalSplit(GetErrorHeight(), error_bounds, elements_bounds);
2643*f6aab3d8Srobert     Surface error_surface = pad.SubSurface(error_bounds);
2644*f6aab3d8Srobert     Surface elements_surface = pad.SubSurface(elements_bounds);
2645be691f3bSpatrick 
2646be691f3bSpatrick     DrawError(error_surface);
2647be691f3bSpatrick     DrawElements(elements_surface);
2648be691f3bSpatrick 
2649be691f3bSpatrick     int copy_height = std::min(surface.GetHeight(), pad.GetHeight());
2650be691f3bSpatrick     pad.CopyToSurface(surface, Point(0, m_first_visible_line), Point(),
2651be691f3bSpatrick                       Size(width, copy_height));
2652be691f3bSpatrick   }
2653be691f3bSpatrick 
DrawSubmitHint(Surface & surface,bool is_active)2654*f6aab3d8Srobert   void DrawSubmitHint(Surface &surface, bool is_active) {
2655*f6aab3d8Srobert     surface.MoveCursor(2, surface.GetHeight() - 1);
2656*f6aab3d8Srobert     if (is_active)
2657*f6aab3d8Srobert       surface.AttributeOn(A_BOLD | COLOR_PAIR(BlackOnWhite));
2658*f6aab3d8Srobert     surface.Printf("[Press Alt+Enter to %s]",
2659*f6aab3d8Srobert                    m_delegate_sp->GetAction(0).GetLabel().c_str());
2660*f6aab3d8Srobert     if (is_active)
2661*f6aab3d8Srobert       surface.AttributeOff(A_BOLD | COLOR_PAIR(BlackOnWhite));
2662*f6aab3d8Srobert   }
2663*f6aab3d8Srobert 
WindowDelegateDraw(Window & window,bool force)2664be691f3bSpatrick   bool WindowDelegateDraw(Window &window, bool force) override {
2665be691f3bSpatrick     m_delegate_sp->UpdateFieldsVisibility();
2666be691f3bSpatrick 
2667be691f3bSpatrick     window.Erase();
2668be691f3bSpatrick 
2669be691f3bSpatrick     window.DrawTitleBox(m_delegate_sp->GetName().c_str(),
2670*f6aab3d8Srobert                         "Press Esc to Cancel");
2671*f6aab3d8Srobert     DrawSubmitHint(window, window.IsActive());
2672be691f3bSpatrick 
2673be691f3bSpatrick     Rect content_bounds = window.GetFrame();
2674be691f3bSpatrick     content_bounds.Inset(2, 2);
2675*f6aab3d8Srobert     Surface content_surface = window.SubSurface(content_bounds);
2676be691f3bSpatrick 
2677be691f3bSpatrick     DrawContent(content_surface);
2678be691f3bSpatrick     return true;
2679be691f3bSpatrick   }
2680be691f3bSpatrick 
SkipNextHiddenFields()2681be691f3bSpatrick   void SkipNextHiddenFields() {
2682be691f3bSpatrick     while (true) {
2683be691f3bSpatrick       if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2684be691f3bSpatrick         return;
2685be691f3bSpatrick 
2686be691f3bSpatrick       if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2687be691f3bSpatrick         m_selection_type = SelectionType::Action;
2688be691f3bSpatrick         m_selection_index = 0;
2689be691f3bSpatrick         return;
2690be691f3bSpatrick       }
2691be691f3bSpatrick 
2692be691f3bSpatrick       m_selection_index++;
2693be691f3bSpatrick     }
2694be691f3bSpatrick   }
2695be691f3bSpatrick 
SelectNext(int key)2696be691f3bSpatrick   HandleCharResult SelectNext(int key) {
2697be691f3bSpatrick     if (m_selection_type == SelectionType::Action) {
2698be691f3bSpatrick       if (m_selection_index < m_delegate_sp->GetNumberOfActions() - 1) {
2699be691f3bSpatrick         m_selection_index++;
2700be691f3bSpatrick         return eKeyHandled;
2701be691f3bSpatrick       }
2702be691f3bSpatrick 
2703be691f3bSpatrick       m_selection_index = 0;
2704be691f3bSpatrick       m_selection_type = SelectionType::Field;
2705be691f3bSpatrick       SkipNextHiddenFields();
2706be691f3bSpatrick       if (m_selection_type == SelectionType::Field) {
2707be691f3bSpatrick         FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2708be691f3bSpatrick         next_field->FieldDelegateSelectFirstElement();
2709be691f3bSpatrick       }
2710be691f3bSpatrick       return eKeyHandled;
2711be691f3bSpatrick     }
2712be691f3bSpatrick 
2713be691f3bSpatrick     FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2714be691f3bSpatrick     if (!field->FieldDelegateOnLastOrOnlyElement()) {
2715be691f3bSpatrick       return field->FieldDelegateHandleChar(key);
2716be691f3bSpatrick     }
2717be691f3bSpatrick 
2718be691f3bSpatrick     field->FieldDelegateExitCallback();
2719be691f3bSpatrick 
2720be691f3bSpatrick     if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2721be691f3bSpatrick       m_selection_type = SelectionType::Action;
2722be691f3bSpatrick       m_selection_index = 0;
2723be691f3bSpatrick       return eKeyHandled;
2724be691f3bSpatrick     }
2725be691f3bSpatrick 
2726be691f3bSpatrick     m_selection_index++;
2727be691f3bSpatrick     SkipNextHiddenFields();
2728be691f3bSpatrick 
2729be691f3bSpatrick     if (m_selection_type == SelectionType::Field) {
2730be691f3bSpatrick       FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2731be691f3bSpatrick       next_field->FieldDelegateSelectFirstElement();
2732be691f3bSpatrick     }
2733be691f3bSpatrick 
2734be691f3bSpatrick     return eKeyHandled;
2735be691f3bSpatrick   }
2736be691f3bSpatrick 
SkipPreviousHiddenFields()2737be691f3bSpatrick   void SkipPreviousHiddenFields() {
2738be691f3bSpatrick     while (true) {
2739be691f3bSpatrick       if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2740be691f3bSpatrick         return;
2741be691f3bSpatrick 
2742be691f3bSpatrick       if (m_selection_index == 0) {
2743be691f3bSpatrick         m_selection_type = SelectionType::Action;
2744be691f3bSpatrick         m_selection_index = 0;
2745be691f3bSpatrick         return;
2746be691f3bSpatrick       }
2747be691f3bSpatrick 
2748be691f3bSpatrick       m_selection_index--;
2749be691f3bSpatrick     }
2750be691f3bSpatrick   }
2751be691f3bSpatrick 
SelectPrevious(int key)2752be691f3bSpatrick   HandleCharResult SelectPrevious(int key) {
2753be691f3bSpatrick     if (m_selection_type == SelectionType::Action) {
2754be691f3bSpatrick       if (m_selection_index > 0) {
2755be691f3bSpatrick         m_selection_index--;
2756be691f3bSpatrick         return eKeyHandled;
2757be691f3bSpatrick       }
2758be691f3bSpatrick       m_selection_index = m_delegate_sp->GetNumberOfFields() - 1;
2759be691f3bSpatrick       m_selection_type = SelectionType::Field;
2760be691f3bSpatrick       SkipPreviousHiddenFields();
2761be691f3bSpatrick       if (m_selection_type == SelectionType::Field) {
2762be691f3bSpatrick         FieldDelegate *previous_field =
2763be691f3bSpatrick             m_delegate_sp->GetField(m_selection_index);
2764be691f3bSpatrick         previous_field->FieldDelegateSelectLastElement();
2765be691f3bSpatrick       }
2766be691f3bSpatrick       return eKeyHandled;
2767be691f3bSpatrick     }
2768be691f3bSpatrick 
2769be691f3bSpatrick     FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2770be691f3bSpatrick     if (!field->FieldDelegateOnFirstOrOnlyElement()) {
2771be691f3bSpatrick       return field->FieldDelegateHandleChar(key);
2772be691f3bSpatrick     }
2773be691f3bSpatrick 
2774be691f3bSpatrick     field->FieldDelegateExitCallback();
2775be691f3bSpatrick 
2776be691f3bSpatrick     if (m_selection_index == 0) {
2777be691f3bSpatrick       m_selection_type = SelectionType::Action;
2778be691f3bSpatrick       m_selection_index = m_delegate_sp->GetNumberOfActions() - 1;
2779be691f3bSpatrick       return eKeyHandled;
2780be691f3bSpatrick     }
2781be691f3bSpatrick 
2782be691f3bSpatrick     m_selection_index--;
2783be691f3bSpatrick     SkipPreviousHiddenFields();
2784be691f3bSpatrick 
2785be691f3bSpatrick     if (m_selection_type == SelectionType::Field) {
2786be691f3bSpatrick       FieldDelegate *previous_field =
2787be691f3bSpatrick           m_delegate_sp->GetField(m_selection_index);
2788be691f3bSpatrick       previous_field->FieldDelegateSelectLastElement();
2789be691f3bSpatrick     }
2790be691f3bSpatrick 
2791be691f3bSpatrick     return eKeyHandled;
2792be691f3bSpatrick   }
2793be691f3bSpatrick 
ExecuteAction(Window & window,int index)2794*f6aab3d8Srobert   void ExecuteAction(Window &window, int index) {
2795*f6aab3d8Srobert     FormAction &action = m_delegate_sp->GetAction(index);
2796be691f3bSpatrick     action.Execute(window);
2797be691f3bSpatrick     if (m_delegate_sp->HasError()) {
2798be691f3bSpatrick       m_first_visible_line = 0;
2799be691f3bSpatrick       m_selection_index = 0;
2800be691f3bSpatrick       m_selection_type = SelectionType::Field;
2801be691f3bSpatrick     }
2802be691f3bSpatrick   }
2803be691f3bSpatrick 
2804*f6aab3d8Srobert   // Always return eKeyHandled to absorb all events since forms are always
2805*f6aab3d8Srobert   // added as pop-ups that should take full control until canceled or submitted.
WindowDelegateHandleChar(Window & window,int key)2806be691f3bSpatrick   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
2807be691f3bSpatrick     switch (key) {
2808be691f3bSpatrick     case '\r':
2809be691f3bSpatrick     case '\n':
2810be691f3bSpatrick     case KEY_ENTER:
2811be691f3bSpatrick       if (m_selection_type == SelectionType::Action) {
2812*f6aab3d8Srobert         ExecuteAction(window, m_selection_index);
2813be691f3bSpatrick         return eKeyHandled;
2814be691f3bSpatrick       }
2815be691f3bSpatrick       break;
2816*f6aab3d8Srobert     case KEY_ALT_ENTER:
2817*f6aab3d8Srobert       ExecuteAction(window, 0);
2818*f6aab3d8Srobert       return eKeyHandled;
2819be691f3bSpatrick     case '\t':
2820*f6aab3d8Srobert       SelectNext(key);
2821*f6aab3d8Srobert       return eKeyHandled;
2822be691f3bSpatrick     case KEY_SHIFT_TAB:
2823*f6aab3d8Srobert       SelectPrevious(key);
2824*f6aab3d8Srobert       return eKeyHandled;
2825be691f3bSpatrick     case KEY_ESCAPE:
2826be691f3bSpatrick       window.GetParent()->RemoveSubWindow(&window);
2827be691f3bSpatrick       return eKeyHandled;
2828be691f3bSpatrick     default:
2829be691f3bSpatrick       break;
2830be691f3bSpatrick     }
2831be691f3bSpatrick 
2832be691f3bSpatrick     // If the key wasn't handled and one of the fields is selected, pass the key
2833be691f3bSpatrick     // to that field.
2834be691f3bSpatrick     if (m_selection_type == SelectionType::Field) {
2835be691f3bSpatrick       FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2836*f6aab3d8Srobert       if (field->FieldDelegateHandleChar(key) == eKeyHandled)
2837*f6aab3d8Srobert         return eKeyHandled;
2838be691f3bSpatrick     }
2839be691f3bSpatrick 
2840*f6aab3d8Srobert     // If the key wasn't handled by the possibly selected field, handle some
2841*f6aab3d8Srobert     // extra keys for navigation.
2842*f6aab3d8Srobert     switch (key) {
2843*f6aab3d8Srobert     case KEY_DOWN:
2844*f6aab3d8Srobert       SelectNext(key);
2845*f6aab3d8Srobert       return eKeyHandled;
2846*f6aab3d8Srobert     case KEY_UP:
2847*f6aab3d8Srobert       SelectPrevious(key);
2848*f6aab3d8Srobert       return eKeyHandled;
2849*f6aab3d8Srobert     default:
2850*f6aab3d8Srobert       break;
2851*f6aab3d8Srobert     }
2852*f6aab3d8Srobert 
2853*f6aab3d8Srobert     return eKeyHandled;
2854be691f3bSpatrick   }
2855be691f3bSpatrick 
2856be691f3bSpatrick protected:
2857be691f3bSpatrick   FormDelegateSP m_delegate_sp;
2858be691f3bSpatrick   // The index of the currently selected SelectionType.
2859*f6aab3d8Srobert   int m_selection_index = 0;
2860be691f3bSpatrick   // See SelectionType class enum.
2861be691f3bSpatrick   SelectionType m_selection_type;
2862be691f3bSpatrick   // The first visible line from the pad.
2863*f6aab3d8Srobert   int m_first_visible_line = 0;
2864be691f3bSpatrick };
2865be691f3bSpatrick 
2866be691f3bSpatrick ///////////////////////////
2867be691f3bSpatrick // Form Delegate Instances
2868be691f3bSpatrick ///////////////////////////
2869be691f3bSpatrick 
2870be691f3bSpatrick class DetachOrKillProcessFormDelegate : public FormDelegate {
2871be691f3bSpatrick public:
DetachOrKillProcessFormDelegate(Process * process)2872be691f3bSpatrick   DetachOrKillProcessFormDelegate(Process *process) : m_process(process) {
2873be691f3bSpatrick     SetError("There is a running process, either detach or kill it.");
2874be691f3bSpatrick 
2875be691f3bSpatrick     m_keep_stopped_field =
2876be691f3bSpatrick         AddBooleanField("Keep process stopped when detaching.", false);
2877be691f3bSpatrick 
2878be691f3bSpatrick     AddAction("Detach", [this](Window &window) { Detach(window); });
2879be691f3bSpatrick     AddAction("Kill", [this](Window &window) { Kill(window); });
2880be691f3bSpatrick   }
2881be691f3bSpatrick 
GetName()2882be691f3bSpatrick   std::string GetName() override { return "Detach/Kill Process"; }
2883be691f3bSpatrick 
Kill(Window & window)2884be691f3bSpatrick   void Kill(Window &window) {
2885be691f3bSpatrick     Status destroy_status(m_process->Destroy(false));
2886be691f3bSpatrick     if (destroy_status.Fail()) {
2887be691f3bSpatrick       SetError("Failed to kill process.");
2888be691f3bSpatrick       return;
2889be691f3bSpatrick     }
2890be691f3bSpatrick     window.GetParent()->RemoveSubWindow(&window);
2891be691f3bSpatrick   }
2892be691f3bSpatrick 
Detach(Window & window)2893be691f3bSpatrick   void Detach(Window &window) {
2894be691f3bSpatrick     Status detach_status(m_process->Detach(m_keep_stopped_field->GetBoolean()));
2895be691f3bSpatrick     if (detach_status.Fail()) {
2896be691f3bSpatrick       SetError("Failed to detach from process.");
2897be691f3bSpatrick       return;
2898be691f3bSpatrick     }
2899be691f3bSpatrick     window.GetParent()->RemoveSubWindow(&window);
2900be691f3bSpatrick   }
2901be691f3bSpatrick 
2902be691f3bSpatrick protected:
2903be691f3bSpatrick   Process *m_process;
2904be691f3bSpatrick   BooleanFieldDelegate *m_keep_stopped_field;
2905be691f3bSpatrick };
2906be691f3bSpatrick 
2907be691f3bSpatrick class ProcessAttachFormDelegate : public FormDelegate {
2908be691f3bSpatrick public:
ProcessAttachFormDelegate(Debugger & debugger,WindowSP main_window_sp)2909be691f3bSpatrick   ProcessAttachFormDelegate(Debugger &debugger, WindowSP main_window_sp)
2910be691f3bSpatrick       : m_debugger(debugger), m_main_window_sp(main_window_sp) {
2911be691f3bSpatrick     std::vector<std::string> types;
2912be691f3bSpatrick     types.push_back(std::string("Name"));
2913be691f3bSpatrick     types.push_back(std::string("PID"));
2914be691f3bSpatrick     m_type_field = AddChoicesField("Attach By", 2, types);
2915be691f3bSpatrick     m_pid_field = AddIntegerField("PID", 0, true);
2916be691f3bSpatrick     m_name_field =
2917be691f3bSpatrick         AddTextField("Process Name", GetDefaultProcessName().c_str(), true);
2918be691f3bSpatrick     m_continue_field = AddBooleanField("Continue once attached.", false);
2919be691f3bSpatrick     m_wait_for_field = AddBooleanField("Wait for process to launch.", false);
2920be691f3bSpatrick     m_include_existing_field =
2921be691f3bSpatrick         AddBooleanField("Include existing processes.", false);
2922be691f3bSpatrick     m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
2923be691f3bSpatrick     m_plugin_field = AddProcessPluginField();
2924be691f3bSpatrick 
2925be691f3bSpatrick     AddAction("Attach", [this](Window &window) { Attach(window); });
2926be691f3bSpatrick   }
2927be691f3bSpatrick 
GetName()2928be691f3bSpatrick   std::string GetName() override { return "Attach Process"; }
2929be691f3bSpatrick 
UpdateFieldsVisibility()2930be691f3bSpatrick   void UpdateFieldsVisibility() override {
2931be691f3bSpatrick     if (m_type_field->GetChoiceContent() == "Name") {
2932be691f3bSpatrick       m_pid_field->FieldDelegateHide();
2933be691f3bSpatrick       m_name_field->FieldDelegateShow();
2934be691f3bSpatrick       m_wait_for_field->FieldDelegateShow();
2935be691f3bSpatrick       if (m_wait_for_field->GetBoolean())
2936be691f3bSpatrick         m_include_existing_field->FieldDelegateShow();
2937be691f3bSpatrick       else
2938be691f3bSpatrick         m_include_existing_field->FieldDelegateHide();
2939be691f3bSpatrick     } else {
2940be691f3bSpatrick       m_pid_field->FieldDelegateShow();
2941be691f3bSpatrick       m_name_field->FieldDelegateHide();
2942be691f3bSpatrick       m_wait_for_field->FieldDelegateHide();
2943be691f3bSpatrick       m_include_existing_field->FieldDelegateHide();
2944be691f3bSpatrick     }
2945be691f3bSpatrick     if (m_show_advanced_field->GetBoolean())
2946be691f3bSpatrick       m_plugin_field->FieldDelegateShow();
2947be691f3bSpatrick     else
2948be691f3bSpatrick       m_plugin_field->FieldDelegateHide();
2949be691f3bSpatrick   }
2950be691f3bSpatrick 
2951be691f3bSpatrick   // Get the basename of the target's main executable if available, empty string
2952be691f3bSpatrick   // otherwise.
GetDefaultProcessName()2953be691f3bSpatrick   std::string GetDefaultProcessName() {
2954be691f3bSpatrick     Target *target = m_debugger.GetSelectedTarget().get();
2955be691f3bSpatrick     if (target == nullptr)
2956be691f3bSpatrick       return "";
2957be691f3bSpatrick 
2958be691f3bSpatrick     ModuleSP module_sp = target->GetExecutableModule();
2959be691f3bSpatrick     if (!module_sp->IsExecutable())
2960be691f3bSpatrick       return "";
2961be691f3bSpatrick 
2962be691f3bSpatrick     return module_sp->GetFileSpec().GetFilename().AsCString();
2963be691f3bSpatrick   }
2964be691f3bSpatrick 
StopRunningProcess()2965be691f3bSpatrick   bool StopRunningProcess() {
2966be691f3bSpatrick     ExecutionContext exe_ctx =
2967be691f3bSpatrick         m_debugger.GetCommandInterpreter().GetExecutionContext();
2968be691f3bSpatrick 
2969be691f3bSpatrick     if (!exe_ctx.HasProcessScope())
2970be691f3bSpatrick       return false;
2971be691f3bSpatrick 
2972be691f3bSpatrick     Process *process = exe_ctx.GetProcessPtr();
2973be691f3bSpatrick     if (!(process && process->IsAlive()))
2974be691f3bSpatrick       return false;
2975be691f3bSpatrick 
2976be691f3bSpatrick     FormDelegateSP form_delegate_sp =
2977be691f3bSpatrick         FormDelegateSP(new DetachOrKillProcessFormDelegate(process));
2978be691f3bSpatrick     Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);
2979be691f3bSpatrick     WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(
2980be691f3bSpatrick         form_delegate_sp->GetName().c_str(), bounds, true);
2981be691f3bSpatrick     WindowDelegateSP window_delegate_sp =
2982be691f3bSpatrick         WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
2983be691f3bSpatrick     form_window_sp->SetDelegate(window_delegate_sp);
2984be691f3bSpatrick 
2985be691f3bSpatrick     return true;
2986be691f3bSpatrick   }
2987be691f3bSpatrick 
GetTarget()2988be691f3bSpatrick   Target *GetTarget() {
2989be691f3bSpatrick     Target *target = m_debugger.GetSelectedTarget().get();
2990be691f3bSpatrick 
2991be691f3bSpatrick     if (target != nullptr)
2992be691f3bSpatrick       return target;
2993be691f3bSpatrick 
2994be691f3bSpatrick     TargetSP new_target_sp;
2995be691f3bSpatrick     m_debugger.GetTargetList().CreateTarget(
2996be691f3bSpatrick         m_debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp);
2997be691f3bSpatrick 
2998be691f3bSpatrick     target = new_target_sp.get();
2999be691f3bSpatrick 
3000be691f3bSpatrick     if (target == nullptr)
3001be691f3bSpatrick       SetError("Failed to create target.");
3002be691f3bSpatrick 
3003be691f3bSpatrick     m_debugger.GetTargetList().SetSelectedTarget(new_target_sp);
3004be691f3bSpatrick 
3005be691f3bSpatrick     return target;
3006be691f3bSpatrick   }
3007be691f3bSpatrick 
GetAttachInfo()3008be691f3bSpatrick   ProcessAttachInfo GetAttachInfo() {
3009be691f3bSpatrick     ProcessAttachInfo attach_info;
3010be691f3bSpatrick     attach_info.SetContinueOnceAttached(m_continue_field->GetBoolean());
3011be691f3bSpatrick     if (m_type_field->GetChoiceContent() == "Name") {
3012be691f3bSpatrick       attach_info.GetExecutableFile().SetFile(m_name_field->GetText(),
3013be691f3bSpatrick                                               FileSpec::Style::native);
3014be691f3bSpatrick       attach_info.SetWaitForLaunch(m_wait_for_field->GetBoolean());
3015be691f3bSpatrick       if (m_wait_for_field->GetBoolean())
3016be691f3bSpatrick         attach_info.SetIgnoreExisting(!m_include_existing_field->GetBoolean());
3017be691f3bSpatrick     } else {
3018be691f3bSpatrick       attach_info.SetProcessID(m_pid_field->GetInteger());
3019be691f3bSpatrick     }
3020be691f3bSpatrick     attach_info.SetProcessPluginName(m_plugin_field->GetPluginName());
3021be691f3bSpatrick 
3022be691f3bSpatrick     return attach_info;
3023be691f3bSpatrick   }
3024be691f3bSpatrick 
Attach(Window & window)3025be691f3bSpatrick   void Attach(Window &window) {
3026be691f3bSpatrick     ClearError();
3027be691f3bSpatrick 
3028be691f3bSpatrick     bool all_fields_are_valid = CheckFieldsValidity();
3029be691f3bSpatrick     if (!all_fields_are_valid)
3030be691f3bSpatrick       return;
3031be691f3bSpatrick 
3032be691f3bSpatrick     bool process_is_running = StopRunningProcess();
3033be691f3bSpatrick     if (process_is_running)
3034be691f3bSpatrick       return;
3035be691f3bSpatrick 
3036be691f3bSpatrick     Target *target = GetTarget();
3037be691f3bSpatrick     if (HasError())
3038be691f3bSpatrick       return;
3039be691f3bSpatrick 
3040be691f3bSpatrick     StreamString stream;
3041be691f3bSpatrick     ProcessAttachInfo attach_info = GetAttachInfo();
3042be691f3bSpatrick     Status status = target->Attach(attach_info, &stream);
3043be691f3bSpatrick 
3044be691f3bSpatrick     if (status.Fail()) {
3045be691f3bSpatrick       SetError(status.AsCString());
3046be691f3bSpatrick       return;
3047be691f3bSpatrick     }
3048be691f3bSpatrick 
3049be691f3bSpatrick     ProcessSP process_sp(target->GetProcessSP());
3050be691f3bSpatrick     if (!process_sp) {
3051be691f3bSpatrick       SetError("Attached sucessfully but target has no process.");
3052be691f3bSpatrick       return;
3053be691f3bSpatrick     }
3054be691f3bSpatrick 
3055be691f3bSpatrick     if (attach_info.GetContinueOnceAttached())
3056be691f3bSpatrick       process_sp->Resume();
3057be691f3bSpatrick 
3058be691f3bSpatrick     window.GetParent()->RemoveSubWindow(&window);
3059be691f3bSpatrick   }
3060be691f3bSpatrick 
3061be691f3bSpatrick protected:
3062be691f3bSpatrick   Debugger &m_debugger;
3063be691f3bSpatrick   WindowSP m_main_window_sp;
3064be691f3bSpatrick 
3065be691f3bSpatrick   ChoicesFieldDelegate *m_type_field;
3066be691f3bSpatrick   IntegerFieldDelegate *m_pid_field;
3067be691f3bSpatrick   TextFieldDelegate *m_name_field;
3068be691f3bSpatrick   BooleanFieldDelegate *m_continue_field;
3069be691f3bSpatrick   BooleanFieldDelegate *m_wait_for_field;
3070be691f3bSpatrick   BooleanFieldDelegate *m_include_existing_field;
3071be691f3bSpatrick   BooleanFieldDelegate *m_show_advanced_field;
3072be691f3bSpatrick   ProcessPluginFieldDelegate *m_plugin_field;
3073be691f3bSpatrick };
3074be691f3bSpatrick 
3075*f6aab3d8Srobert class TargetCreateFormDelegate : public FormDelegate {
3076*f6aab3d8Srobert public:
TargetCreateFormDelegate(Debugger & debugger)3077*f6aab3d8Srobert   TargetCreateFormDelegate(Debugger &debugger) : m_debugger(debugger) {
3078*f6aab3d8Srobert     m_executable_field = AddFileField("Executable", "", /*need_to_exist=*/true,
3079*f6aab3d8Srobert                                       /*required=*/true);
3080*f6aab3d8Srobert     m_core_file_field = AddFileField("Core File", "", /*need_to_exist=*/true,
3081*f6aab3d8Srobert                                      /*required=*/false);
3082*f6aab3d8Srobert     m_symbol_file_field = AddFileField(
3083*f6aab3d8Srobert         "Symbol File", "", /*need_to_exist=*/true, /*required=*/false);
3084*f6aab3d8Srobert     m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
3085*f6aab3d8Srobert     m_remote_file_field = AddFileField(
3086*f6aab3d8Srobert         "Remote File", "", /*need_to_exist=*/false, /*required=*/false);
3087*f6aab3d8Srobert     m_arch_field = AddArchField("Architecture", "", /*required=*/false);
3088*f6aab3d8Srobert     m_platform_field = AddPlatformPluginField(debugger);
3089*f6aab3d8Srobert     m_load_dependent_files_field =
3090*f6aab3d8Srobert         AddChoicesField("Load Dependents", 3, GetLoadDependentFilesChoices());
3091*f6aab3d8Srobert 
3092*f6aab3d8Srobert     AddAction("Create", [this](Window &window) { CreateTarget(window); });
3093*f6aab3d8Srobert   }
3094*f6aab3d8Srobert 
GetName()3095*f6aab3d8Srobert   std::string GetName() override { return "Create Target"; }
3096*f6aab3d8Srobert 
UpdateFieldsVisibility()3097*f6aab3d8Srobert   void UpdateFieldsVisibility() override {
3098*f6aab3d8Srobert     if (m_show_advanced_field->GetBoolean()) {
3099*f6aab3d8Srobert       m_remote_file_field->FieldDelegateShow();
3100*f6aab3d8Srobert       m_arch_field->FieldDelegateShow();
3101*f6aab3d8Srobert       m_platform_field->FieldDelegateShow();
3102*f6aab3d8Srobert       m_load_dependent_files_field->FieldDelegateShow();
3103*f6aab3d8Srobert     } else {
3104*f6aab3d8Srobert       m_remote_file_field->FieldDelegateHide();
3105*f6aab3d8Srobert       m_arch_field->FieldDelegateHide();
3106*f6aab3d8Srobert       m_platform_field->FieldDelegateHide();
3107*f6aab3d8Srobert       m_load_dependent_files_field->FieldDelegateHide();
3108*f6aab3d8Srobert     }
3109*f6aab3d8Srobert   }
3110*f6aab3d8Srobert 
3111*f6aab3d8Srobert   static constexpr const char *kLoadDependentFilesNo = "No";
3112*f6aab3d8Srobert   static constexpr const char *kLoadDependentFilesYes = "Yes";
3113*f6aab3d8Srobert   static constexpr const char *kLoadDependentFilesExecOnly = "Executable only";
3114*f6aab3d8Srobert 
GetLoadDependentFilesChoices()3115*f6aab3d8Srobert   std::vector<std::string> GetLoadDependentFilesChoices() {
3116*f6aab3d8Srobert     std::vector<std::string> load_dependents_options;
3117*f6aab3d8Srobert     load_dependents_options.push_back(kLoadDependentFilesExecOnly);
3118*f6aab3d8Srobert     load_dependents_options.push_back(kLoadDependentFilesYes);
3119*f6aab3d8Srobert     load_dependents_options.push_back(kLoadDependentFilesNo);
3120*f6aab3d8Srobert     return load_dependents_options;
3121*f6aab3d8Srobert   }
3122*f6aab3d8Srobert 
GetLoadDependentFiles()3123*f6aab3d8Srobert   LoadDependentFiles GetLoadDependentFiles() {
3124*f6aab3d8Srobert     std::string choice = m_load_dependent_files_field->GetChoiceContent();
3125*f6aab3d8Srobert     if (choice == kLoadDependentFilesNo)
3126*f6aab3d8Srobert       return eLoadDependentsNo;
3127*f6aab3d8Srobert     if (choice == kLoadDependentFilesYes)
3128*f6aab3d8Srobert       return eLoadDependentsYes;
3129*f6aab3d8Srobert     return eLoadDependentsDefault;
3130*f6aab3d8Srobert   }
3131*f6aab3d8Srobert 
GetPlatformOptions()3132*f6aab3d8Srobert   OptionGroupPlatform GetPlatformOptions() {
3133*f6aab3d8Srobert     OptionGroupPlatform platform_options(false);
3134*f6aab3d8Srobert     platform_options.SetPlatformName(m_platform_field->GetPluginName().c_str());
3135*f6aab3d8Srobert     return platform_options;
3136*f6aab3d8Srobert   }
3137*f6aab3d8Srobert 
GetTarget()3138*f6aab3d8Srobert   TargetSP GetTarget() {
3139*f6aab3d8Srobert     OptionGroupPlatform platform_options = GetPlatformOptions();
3140*f6aab3d8Srobert     TargetSP target_sp;
3141*f6aab3d8Srobert     Status status = m_debugger.GetTargetList().CreateTarget(
3142*f6aab3d8Srobert         m_debugger, m_executable_field->GetPath(),
3143*f6aab3d8Srobert         m_arch_field->GetArchString(), GetLoadDependentFiles(),
3144*f6aab3d8Srobert         &platform_options, target_sp);
3145*f6aab3d8Srobert 
3146*f6aab3d8Srobert     if (status.Fail()) {
3147*f6aab3d8Srobert       SetError(status.AsCString());
3148*f6aab3d8Srobert       return nullptr;
3149*f6aab3d8Srobert     }
3150*f6aab3d8Srobert 
3151*f6aab3d8Srobert     m_debugger.GetTargetList().SetSelectedTarget(target_sp);
3152*f6aab3d8Srobert 
3153*f6aab3d8Srobert     return target_sp;
3154*f6aab3d8Srobert   }
3155*f6aab3d8Srobert 
SetSymbolFile(TargetSP target_sp)3156*f6aab3d8Srobert   void SetSymbolFile(TargetSP target_sp) {
3157*f6aab3d8Srobert     if (!m_symbol_file_field->IsSpecified())
3158*f6aab3d8Srobert       return;
3159*f6aab3d8Srobert 
3160*f6aab3d8Srobert     ModuleSP module_sp(target_sp->GetExecutableModule());
3161*f6aab3d8Srobert     if (!module_sp)
3162*f6aab3d8Srobert       return;
3163*f6aab3d8Srobert 
3164*f6aab3d8Srobert     module_sp->SetSymbolFileFileSpec(
3165*f6aab3d8Srobert         m_symbol_file_field->GetResolvedFileSpec());
3166*f6aab3d8Srobert   }
3167*f6aab3d8Srobert 
SetCoreFile(TargetSP target_sp)3168*f6aab3d8Srobert   void SetCoreFile(TargetSP target_sp) {
3169*f6aab3d8Srobert     if (!m_core_file_field->IsSpecified())
3170*f6aab3d8Srobert       return;
3171*f6aab3d8Srobert 
3172*f6aab3d8Srobert     FileSpec core_file_spec = m_core_file_field->GetResolvedFileSpec();
3173*f6aab3d8Srobert 
3174*f6aab3d8Srobert     FileSpec core_file_directory_spec;
3175*f6aab3d8Srobert     core_file_directory_spec.SetDirectory(core_file_spec.GetDirectory());
3176*f6aab3d8Srobert     target_sp->AppendExecutableSearchPaths(core_file_directory_spec);
3177*f6aab3d8Srobert 
3178*f6aab3d8Srobert     ProcessSP process_sp(target_sp->CreateProcess(
3179*f6aab3d8Srobert         m_debugger.GetListener(), llvm::StringRef(), &core_file_spec, false));
3180*f6aab3d8Srobert 
3181*f6aab3d8Srobert     if (!process_sp) {
3182*f6aab3d8Srobert       SetError("Unable to find process plug-in for core file!");
3183*f6aab3d8Srobert       return;
3184*f6aab3d8Srobert     }
3185*f6aab3d8Srobert 
3186*f6aab3d8Srobert     Status status = process_sp->LoadCore();
3187*f6aab3d8Srobert     if (status.Fail()) {
3188*f6aab3d8Srobert       SetError("Can't find plug-in for core file!");
3189*f6aab3d8Srobert       return;
3190*f6aab3d8Srobert     }
3191*f6aab3d8Srobert   }
3192*f6aab3d8Srobert 
SetRemoteFile(TargetSP target_sp)3193*f6aab3d8Srobert   void SetRemoteFile(TargetSP target_sp) {
3194*f6aab3d8Srobert     if (!m_remote_file_field->IsSpecified())
3195*f6aab3d8Srobert       return;
3196*f6aab3d8Srobert 
3197*f6aab3d8Srobert     ModuleSP module_sp(target_sp->GetExecutableModule());
3198*f6aab3d8Srobert     if (!module_sp)
3199*f6aab3d8Srobert       return;
3200*f6aab3d8Srobert 
3201*f6aab3d8Srobert     FileSpec remote_file_spec = m_remote_file_field->GetFileSpec();
3202*f6aab3d8Srobert     module_sp->SetPlatformFileSpec(remote_file_spec);
3203*f6aab3d8Srobert   }
3204*f6aab3d8Srobert 
RemoveTarget(TargetSP target_sp)3205*f6aab3d8Srobert   void RemoveTarget(TargetSP target_sp) {
3206*f6aab3d8Srobert     m_debugger.GetTargetList().DeleteTarget(target_sp);
3207*f6aab3d8Srobert   }
3208*f6aab3d8Srobert 
CreateTarget(Window & window)3209*f6aab3d8Srobert   void CreateTarget(Window &window) {
3210*f6aab3d8Srobert     ClearError();
3211*f6aab3d8Srobert 
3212*f6aab3d8Srobert     bool all_fields_are_valid = CheckFieldsValidity();
3213*f6aab3d8Srobert     if (!all_fields_are_valid)
3214*f6aab3d8Srobert       return;
3215*f6aab3d8Srobert 
3216*f6aab3d8Srobert     TargetSP target_sp = GetTarget();
3217*f6aab3d8Srobert     if (HasError())
3218*f6aab3d8Srobert       return;
3219*f6aab3d8Srobert 
3220*f6aab3d8Srobert     SetSymbolFile(target_sp);
3221*f6aab3d8Srobert     if (HasError()) {
3222*f6aab3d8Srobert       RemoveTarget(target_sp);
3223*f6aab3d8Srobert       return;
3224*f6aab3d8Srobert     }
3225*f6aab3d8Srobert 
3226*f6aab3d8Srobert     SetCoreFile(target_sp);
3227*f6aab3d8Srobert     if (HasError()) {
3228*f6aab3d8Srobert       RemoveTarget(target_sp);
3229*f6aab3d8Srobert       return;
3230*f6aab3d8Srobert     }
3231*f6aab3d8Srobert 
3232*f6aab3d8Srobert     SetRemoteFile(target_sp);
3233*f6aab3d8Srobert     if (HasError()) {
3234*f6aab3d8Srobert       RemoveTarget(target_sp);
3235*f6aab3d8Srobert       return;
3236*f6aab3d8Srobert     }
3237*f6aab3d8Srobert 
3238*f6aab3d8Srobert     window.GetParent()->RemoveSubWindow(&window);
3239*f6aab3d8Srobert   }
3240*f6aab3d8Srobert 
3241*f6aab3d8Srobert protected:
3242*f6aab3d8Srobert   Debugger &m_debugger;
3243*f6aab3d8Srobert 
3244*f6aab3d8Srobert   FileFieldDelegate *m_executable_field;
3245*f6aab3d8Srobert   FileFieldDelegate *m_core_file_field;
3246*f6aab3d8Srobert   FileFieldDelegate *m_symbol_file_field;
3247*f6aab3d8Srobert   BooleanFieldDelegate *m_show_advanced_field;
3248*f6aab3d8Srobert   FileFieldDelegate *m_remote_file_field;
3249*f6aab3d8Srobert   ArchFieldDelegate *m_arch_field;
3250*f6aab3d8Srobert   PlatformPluginFieldDelegate *m_platform_field;
3251*f6aab3d8Srobert   ChoicesFieldDelegate *m_load_dependent_files_field;
3252*f6aab3d8Srobert };
3253*f6aab3d8Srobert 
3254*f6aab3d8Srobert class ProcessLaunchFormDelegate : public FormDelegate {
3255*f6aab3d8Srobert public:
ProcessLaunchFormDelegate(Debugger & debugger,WindowSP main_window_sp)3256*f6aab3d8Srobert   ProcessLaunchFormDelegate(Debugger &debugger, WindowSP main_window_sp)
3257*f6aab3d8Srobert       : m_debugger(debugger), m_main_window_sp(main_window_sp) {
3258*f6aab3d8Srobert 
3259*f6aab3d8Srobert     m_arguments_field = AddArgumentsField();
3260*f6aab3d8Srobert     SetArgumentsFieldDefaultValue();
3261*f6aab3d8Srobert     m_target_environment_field =
3262*f6aab3d8Srobert         AddEnvironmentVariableListField("Target Environment Variables");
3263*f6aab3d8Srobert     SetTargetEnvironmentFieldDefaultValue();
3264*f6aab3d8Srobert     m_working_directory_field = AddDirectoryField(
3265*f6aab3d8Srobert         "Working Directory", GetDefaultWorkingDirectory().c_str(), true, false);
3266*f6aab3d8Srobert 
3267*f6aab3d8Srobert     m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
3268*f6aab3d8Srobert 
3269*f6aab3d8Srobert     m_stop_at_entry_field = AddBooleanField("Stop at entry point.", false);
3270*f6aab3d8Srobert     m_detach_on_error_field =
3271*f6aab3d8Srobert         AddBooleanField("Detach on error.", GetDefaultDetachOnError());
3272*f6aab3d8Srobert     m_disable_aslr_field =
3273*f6aab3d8Srobert         AddBooleanField("Disable ASLR", GetDefaultDisableASLR());
3274*f6aab3d8Srobert     m_plugin_field = AddProcessPluginField();
3275*f6aab3d8Srobert     m_arch_field = AddArchField("Architecture", "", false);
3276*f6aab3d8Srobert     m_shell_field = AddFileField("Shell", "", true, false);
3277*f6aab3d8Srobert     m_expand_shell_arguments_field =
3278*f6aab3d8Srobert         AddBooleanField("Expand shell arguments.", false);
3279*f6aab3d8Srobert 
3280*f6aab3d8Srobert     m_disable_standard_io_field =
3281*f6aab3d8Srobert         AddBooleanField("Disable Standard IO", GetDefaultDisableStandardIO());
3282*f6aab3d8Srobert     m_standard_output_field =
3283*f6aab3d8Srobert         AddFileField("Standard Output File", "", /*need_to_exist=*/false,
3284*f6aab3d8Srobert                      /*required=*/false);
3285*f6aab3d8Srobert     m_standard_error_field =
3286*f6aab3d8Srobert         AddFileField("Standard Error File", "", /*need_to_exist=*/false,
3287*f6aab3d8Srobert                      /*required=*/false);
3288*f6aab3d8Srobert     m_standard_input_field =
3289*f6aab3d8Srobert         AddFileField("Standard Input File", "", /*need_to_exist=*/false,
3290*f6aab3d8Srobert                      /*required=*/false);
3291*f6aab3d8Srobert 
3292*f6aab3d8Srobert     m_show_inherited_environment_field =
3293*f6aab3d8Srobert         AddBooleanField("Show inherited environment variables.", false);
3294*f6aab3d8Srobert     m_inherited_environment_field =
3295*f6aab3d8Srobert         AddEnvironmentVariableListField("Inherited Environment Variables");
3296*f6aab3d8Srobert     SetInheritedEnvironmentFieldDefaultValue();
3297*f6aab3d8Srobert 
3298*f6aab3d8Srobert     AddAction("Launch", [this](Window &window) { Launch(window); });
3299*f6aab3d8Srobert   }
3300*f6aab3d8Srobert 
GetName()3301*f6aab3d8Srobert   std::string GetName() override { return "Launch Process"; }
3302*f6aab3d8Srobert 
UpdateFieldsVisibility()3303*f6aab3d8Srobert   void UpdateFieldsVisibility() override {
3304*f6aab3d8Srobert     if (m_show_advanced_field->GetBoolean()) {
3305*f6aab3d8Srobert       m_stop_at_entry_field->FieldDelegateShow();
3306*f6aab3d8Srobert       m_detach_on_error_field->FieldDelegateShow();
3307*f6aab3d8Srobert       m_disable_aslr_field->FieldDelegateShow();
3308*f6aab3d8Srobert       m_plugin_field->FieldDelegateShow();
3309*f6aab3d8Srobert       m_arch_field->FieldDelegateShow();
3310*f6aab3d8Srobert       m_shell_field->FieldDelegateShow();
3311*f6aab3d8Srobert       m_expand_shell_arguments_field->FieldDelegateShow();
3312*f6aab3d8Srobert       m_disable_standard_io_field->FieldDelegateShow();
3313*f6aab3d8Srobert       if (m_disable_standard_io_field->GetBoolean()) {
3314*f6aab3d8Srobert         m_standard_input_field->FieldDelegateHide();
3315*f6aab3d8Srobert         m_standard_output_field->FieldDelegateHide();
3316*f6aab3d8Srobert         m_standard_error_field->FieldDelegateHide();
3317*f6aab3d8Srobert       } else {
3318*f6aab3d8Srobert         m_standard_input_field->FieldDelegateShow();
3319*f6aab3d8Srobert         m_standard_output_field->FieldDelegateShow();
3320*f6aab3d8Srobert         m_standard_error_field->FieldDelegateShow();
3321*f6aab3d8Srobert       }
3322*f6aab3d8Srobert       m_show_inherited_environment_field->FieldDelegateShow();
3323*f6aab3d8Srobert       if (m_show_inherited_environment_field->GetBoolean())
3324*f6aab3d8Srobert         m_inherited_environment_field->FieldDelegateShow();
3325*f6aab3d8Srobert       else
3326*f6aab3d8Srobert         m_inherited_environment_field->FieldDelegateHide();
3327*f6aab3d8Srobert     } else {
3328*f6aab3d8Srobert       m_stop_at_entry_field->FieldDelegateHide();
3329*f6aab3d8Srobert       m_detach_on_error_field->FieldDelegateHide();
3330*f6aab3d8Srobert       m_disable_aslr_field->FieldDelegateHide();
3331*f6aab3d8Srobert       m_plugin_field->FieldDelegateHide();
3332*f6aab3d8Srobert       m_arch_field->FieldDelegateHide();
3333*f6aab3d8Srobert       m_shell_field->FieldDelegateHide();
3334*f6aab3d8Srobert       m_expand_shell_arguments_field->FieldDelegateHide();
3335*f6aab3d8Srobert       m_disable_standard_io_field->FieldDelegateHide();
3336*f6aab3d8Srobert       m_standard_input_field->FieldDelegateHide();
3337*f6aab3d8Srobert       m_standard_output_field->FieldDelegateHide();
3338*f6aab3d8Srobert       m_standard_error_field->FieldDelegateHide();
3339*f6aab3d8Srobert       m_show_inherited_environment_field->FieldDelegateHide();
3340*f6aab3d8Srobert       m_inherited_environment_field->FieldDelegateHide();
3341*f6aab3d8Srobert     }
3342*f6aab3d8Srobert   }
3343*f6aab3d8Srobert 
3344*f6aab3d8Srobert   // Methods for setting the default value of the fields.
3345*f6aab3d8Srobert 
SetArgumentsFieldDefaultValue()3346*f6aab3d8Srobert   void SetArgumentsFieldDefaultValue() {
3347*f6aab3d8Srobert     TargetSP target = m_debugger.GetSelectedTarget();
3348*f6aab3d8Srobert     if (target == nullptr)
3349*f6aab3d8Srobert       return;
3350*f6aab3d8Srobert 
3351*f6aab3d8Srobert     const Args &target_arguments =
3352*f6aab3d8Srobert         target->GetProcessLaunchInfo().GetArguments();
3353*f6aab3d8Srobert     m_arguments_field->AddArguments(target_arguments);
3354*f6aab3d8Srobert   }
3355*f6aab3d8Srobert 
SetTargetEnvironmentFieldDefaultValue()3356*f6aab3d8Srobert   void SetTargetEnvironmentFieldDefaultValue() {
3357*f6aab3d8Srobert     TargetSP target = m_debugger.GetSelectedTarget();
3358*f6aab3d8Srobert     if (target == nullptr)
3359*f6aab3d8Srobert       return;
3360*f6aab3d8Srobert 
3361*f6aab3d8Srobert     const Environment &target_environment = target->GetTargetEnvironment();
3362*f6aab3d8Srobert     m_target_environment_field->AddEnvironmentVariables(target_environment);
3363*f6aab3d8Srobert   }
3364*f6aab3d8Srobert 
SetInheritedEnvironmentFieldDefaultValue()3365*f6aab3d8Srobert   void SetInheritedEnvironmentFieldDefaultValue() {
3366*f6aab3d8Srobert     TargetSP target = m_debugger.GetSelectedTarget();
3367*f6aab3d8Srobert     if (target == nullptr)
3368*f6aab3d8Srobert       return;
3369*f6aab3d8Srobert 
3370*f6aab3d8Srobert     const Environment &inherited_environment =
3371*f6aab3d8Srobert         target->GetInheritedEnvironment();
3372*f6aab3d8Srobert     m_inherited_environment_field->AddEnvironmentVariables(
3373*f6aab3d8Srobert         inherited_environment);
3374*f6aab3d8Srobert   }
3375*f6aab3d8Srobert 
GetDefaultWorkingDirectory()3376*f6aab3d8Srobert   std::string GetDefaultWorkingDirectory() {
3377*f6aab3d8Srobert     TargetSP target = m_debugger.GetSelectedTarget();
3378*f6aab3d8Srobert     if (target == nullptr)
3379*f6aab3d8Srobert       return "";
3380*f6aab3d8Srobert 
3381*f6aab3d8Srobert     PlatformSP platform = target->GetPlatform();
3382*f6aab3d8Srobert     return platform->GetWorkingDirectory().GetPath();
3383*f6aab3d8Srobert   }
3384*f6aab3d8Srobert 
GetDefaultDisableASLR()3385*f6aab3d8Srobert   bool GetDefaultDisableASLR() {
3386*f6aab3d8Srobert     TargetSP target = m_debugger.GetSelectedTarget();
3387*f6aab3d8Srobert     if (target == nullptr)
3388*f6aab3d8Srobert       return false;
3389*f6aab3d8Srobert 
3390*f6aab3d8Srobert     return target->GetDisableASLR();
3391*f6aab3d8Srobert   }
3392*f6aab3d8Srobert 
GetDefaultDisableStandardIO()3393*f6aab3d8Srobert   bool GetDefaultDisableStandardIO() {
3394*f6aab3d8Srobert     TargetSP target = m_debugger.GetSelectedTarget();
3395*f6aab3d8Srobert     if (target == nullptr)
3396*f6aab3d8Srobert       return true;
3397*f6aab3d8Srobert 
3398*f6aab3d8Srobert     return target->GetDisableSTDIO();
3399*f6aab3d8Srobert   }
3400*f6aab3d8Srobert 
GetDefaultDetachOnError()3401*f6aab3d8Srobert   bool GetDefaultDetachOnError() {
3402*f6aab3d8Srobert     TargetSP target = m_debugger.GetSelectedTarget();
3403*f6aab3d8Srobert     if (target == nullptr)
3404*f6aab3d8Srobert       return true;
3405*f6aab3d8Srobert 
3406*f6aab3d8Srobert     return target->GetDetachOnError();
3407*f6aab3d8Srobert   }
3408*f6aab3d8Srobert 
3409*f6aab3d8Srobert   // Methods for getting the necessary information and setting them to the
3410*f6aab3d8Srobert   // ProcessLaunchInfo.
3411*f6aab3d8Srobert 
GetExecutableSettings(ProcessLaunchInfo & launch_info)3412*f6aab3d8Srobert   void GetExecutableSettings(ProcessLaunchInfo &launch_info) {
3413*f6aab3d8Srobert     TargetSP target = m_debugger.GetSelectedTarget();
3414*f6aab3d8Srobert     ModuleSP executable_module = target->GetExecutableModule();
3415*f6aab3d8Srobert     llvm::StringRef target_settings_argv0 = target->GetArg0();
3416*f6aab3d8Srobert 
3417*f6aab3d8Srobert     if (!target_settings_argv0.empty()) {
3418*f6aab3d8Srobert       launch_info.GetArguments().AppendArgument(target_settings_argv0);
3419*f6aab3d8Srobert       launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(),
3420*f6aab3d8Srobert                                     false);
3421*f6aab3d8Srobert       return;
3422*f6aab3d8Srobert     }
3423*f6aab3d8Srobert 
3424*f6aab3d8Srobert     launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(),
3425*f6aab3d8Srobert                                   true);
3426*f6aab3d8Srobert   }
3427*f6aab3d8Srobert 
GetArguments(ProcessLaunchInfo & launch_info)3428*f6aab3d8Srobert   void GetArguments(ProcessLaunchInfo &launch_info) {
3429*f6aab3d8Srobert     TargetSP target = m_debugger.GetSelectedTarget();
3430*f6aab3d8Srobert     Args arguments = m_arguments_field->GetArguments();
3431*f6aab3d8Srobert     launch_info.GetArguments().AppendArguments(arguments);
3432*f6aab3d8Srobert   }
3433*f6aab3d8Srobert 
GetEnvironment(ProcessLaunchInfo & launch_info)3434*f6aab3d8Srobert   void GetEnvironment(ProcessLaunchInfo &launch_info) {
3435*f6aab3d8Srobert     Environment target_environment =
3436*f6aab3d8Srobert         m_target_environment_field->GetEnvironment();
3437*f6aab3d8Srobert     Environment inherited_environment =
3438*f6aab3d8Srobert         m_inherited_environment_field->GetEnvironment();
3439*f6aab3d8Srobert     launch_info.GetEnvironment().insert(target_environment.begin(),
3440*f6aab3d8Srobert                                         target_environment.end());
3441*f6aab3d8Srobert     launch_info.GetEnvironment().insert(inherited_environment.begin(),
3442*f6aab3d8Srobert                                         inherited_environment.end());
3443*f6aab3d8Srobert   }
3444*f6aab3d8Srobert 
GetWorkingDirectory(ProcessLaunchInfo & launch_info)3445*f6aab3d8Srobert   void GetWorkingDirectory(ProcessLaunchInfo &launch_info) {
3446*f6aab3d8Srobert     if (m_working_directory_field->IsSpecified())
3447*f6aab3d8Srobert       launch_info.SetWorkingDirectory(
3448*f6aab3d8Srobert           m_working_directory_field->GetResolvedFileSpec());
3449*f6aab3d8Srobert   }
3450*f6aab3d8Srobert 
GetStopAtEntry(ProcessLaunchInfo & launch_info)3451*f6aab3d8Srobert   void GetStopAtEntry(ProcessLaunchInfo &launch_info) {
3452*f6aab3d8Srobert     if (m_stop_at_entry_field->GetBoolean())
3453*f6aab3d8Srobert       launch_info.GetFlags().Set(eLaunchFlagStopAtEntry);
3454*f6aab3d8Srobert     else
3455*f6aab3d8Srobert       launch_info.GetFlags().Clear(eLaunchFlagStopAtEntry);
3456*f6aab3d8Srobert   }
3457*f6aab3d8Srobert 
GetDetachOnError(ProcessLaunchInfo & launch_info)3458*f6aab3d8Srobert   void GetDetachOnError(ProcessLaunchInfo &launch_info) {
3459*f6aab3d8Srobert     if (m_detach_on_error_field->GetBoolean())
3460*f6aab3d8Srobert       launch_info.GetFlags().Set(eLaunchFlagDetachOnError);
3461*f6aab3d8Srobert     else
3462*f6aab3d8Srobert       launch_info.GetFlags().Clear(eLaunchFlagDetachOnError);
3463*f6aab3d8Srobert   }
3464*f6aab3d8Srobert 
GetDisableASLR(ProcessLaunchInfo & launch_info)3465*f6aab3d8Srobert   void GetDisableASLR(ProcessLaunchInfo &launch_info) {
3466*f6aab3d8Srobert     if (m_disable_aslr_field->GetBoolean())
3467*f6aab3d8Srobert       launch_info.GetFlags().Set(eLaunchFlagDisableASLR);
3468*f6aab3d8Srobert     else
3469*f6aab3d8Srobert       launch_info.GetFlags().Clear(eLaunchFlagDisableASLR);
3470*f6aab3d8Srobert   }
3471*f6aab3d8Srobert 
GetPlugin(ProcessLaunchInfo & launch_info)3472*f6aab3d8Srobert   void GetPlugin(ProcessLaunchInfo &launch_info) {
3473*f6aab3d8Srobert     launch_info.SetProcessPluginName(m_plugin_field->GetPluginName());
3474*f6aab3d8Srobert   }
3475*f6aab3d8Srobert 
GetArch(ProcessLaunchInfo & launch_info)3476*f6aab3d8Srobert   void GetArch(ProcessLaunchInfo &launch_info) {
3477*f6aab3d8Srobert     if (!m_arch_field->IsSpecified())
3478*f6aab3d8Srobert       return;
3479*f6aab3d8Srobert 
3480*f6aab3d8Srobert     TargetSP target_sp = m_debugger.GetSelectedTarget();
3481*f6aab3d8Srobert     PlatformSP platform_sp =
3482*f6aab3d8Srobert         target_sp ? target_sp->GetPlatform() : PlatformSP();
3483*f6aab3d8Srobert     launch_info.GetArchitecture() = Platform::GetAugmentedArchSpec(
3484*f6aab3d8Srobert         platform_sp.get(), m_arch_field->GetArchString());
3485*f6aab3d8Srobert   }
3486*f6aab3d8Srobert 
GetShell(ProcessLaunchInfo & launch_info)3487*f6aab3d8Srobert   void GetShell(ProcessLaunchInfo &launch_info) {
3488*f6aab3d8Srobert     if (!m_shell_field->IsSpecified())
3489*f6aab3d8Srobert       return;
3490*f6aab3d8Srobert 
3491*f6aab3d8Srobert     launch_info.SetShell(m_shell_field->GetResolvedFileSpec());
3492*f6aab3d8Srobert     launch_info.SetShellExpandArguments(
3493*f6aab3d8Srobert         m_expand_shell_arguments_field->GetBoolean());
3494*f6aab3d8Srobert   }
3495*f6aab3d8Srobert 
GetStandardIO(ProcessLaunchInfo & launch_info)3496*f6aab3d8Srobert   void GetStandardIO(ProcessLaunchInfo &launch_info) {
3497*f6aab3d8Srobert     if (m_disable_standard_io_field->GetBoolean()) {
3498*f6aab3d8Srobert       launch_info.GetFlags().Set(eLaunchFlagDisableSTDIO);
3499*f6aab3d8Srobert       return;
3500*f6aab3d8Srobert     }
3501*f6aab3d8Srobert 
3502*f6aab3d8Srobert     FileAction action;
3503*f6aab3d8Srobert     if (m_standard_input_field->IsSpecified()) {
3504*f6aab3d8Srobert       if (action.Open(STDIN_FILENO, m_standard_input_field->GetFileSpec(), true,
3505*f6aab3d8Srobert                       false))
3506*f6aab3d8Srobert         launch_info.AppendFileAction(action);
3507*f6aab3d8Srobert     }
3508*f6aab3d8Srobert     if (m_standard_output_field->IsSpecified()) {
3509*f6aab3d8Srobert       if (action.Open(STDOUT_FILENO, m_standard_output_field->GetFileSpec(),
3510*f6aab3d8Srobert                       false, true))
3511*f6aab3d8Srobert         launch_info.AppendFileAction(action);
3512*f6aab3d8Srobert     }
3513*f6aab3d8Srobert     if (m_standard_error_field->IsSpecified()) {
3514*f6aab3d8Srobert       if (action.Open(STDERR_FILENO, m_standard_error_field->GetFileSpec(),
3515*f6aab3d8Srobert                       false, true))
3516*f6aab3d8Srobert         launch_info.AppendFileAction(action);
3517*f6aab3d8Srobert     }
3518*f6aab3d8Srobert   }
3519*f6aab3d8Srobert 
GetInheritTCC(ProcessLaunchInfo & launch_info)3520*f6aab3d8Srobert   void GetInheritTCC(ProcessLaunchInfo &launch_info) {
3521*f6aab3d8Srobert     if (m_debugger.GetSelectedTarget()->GetInheritTCC())
3522*f6aab3d8Srobert       launch_info.GetFlags().Set(eLaunchFlagInheritTCCFromParent);
3523*f6aab3d8Srobert   }
3524*f6aab3d8Srobert 
GetLaunchInfo()3525*f6aab3d8Srobert   ProcessLaunchInfo GetLaunchInfo() {
3526*f6aab3d8Srobert     ProcessLaunchInfo launch_info;
3527*f6aab3d8Srobert 
3528*f6aab3d8Srobert     GetExecutableSettings(launch_info);
3529*f6aab3d8Srobert     GetArguments(launch_info);
3530*f6aab3d8Srobert     GetEnvironment(launch_info);
3531*f6aab3d8Srobert     GetWorkingDirectory(launch_info);
3532*f6aab3d8Srobert     GetStopAtEntry(launch_info);
3533*f6aab3d8Srobert     GetDetachOnError(launch_info);
3534*f6aab3d8Srobert     GetDisableASLR(launch_info);
3535*f6aab3d8Srobert     GetPlugin(launch_info);
3536*f6aab3d8Srobert     GetArch(launch_info);
3537*f6aab3d8Srobert     GetShell(launch_info);
3538*f6aab3d8Srobert     GetStandardIO(launch_info);
3539*f6aab3d8Srobert     GetInheritTCC(launch_info);
3540*f6aab3d8Srobert 
3541*f6aab3d8Srobert     return launch_info;
3542*f6aab3d8Srobert   }
3543*f6aab3d8Srobert 
StopRunningProcess()3544*f6aab3d8Srobert   bool StopRunningProcess() {
3545*f6aab3d8Srobert     ExecutionContext exe_ctx =
3546*f6aab3d8Srobert         m_debugger.GetCommandInterpreter().GetExecutionContext();
3547*f6aab3d8Srobert 
3548*f6aab3d8Srobert     if (!exe_ctx.HasProcessScope())
3549*f6aab3d8Srobert       return false;
3550*f6aab3d8Srobert 
3551*f6aab3d8Srobert     Process *process = exe_ctx.GetProcessPtr();
3552*f6aab3d8Srobert     if (!(process && process->IsAlive()))
3553*f6aab3d8Srobert       return false;
3554*f6aab3d8Srobert 
3555*f6aab3d8Srobert     FormDelegateSP form_delegate_sp =
3556*f6aab3d8Srobert         FormDelegateSP(new DetachOrKillProcessFormDelegate(process));
3557*f6aab3d8Srobert     Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);
3558*f6aab3d8Srobert     WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(
3559*f6aab3d8Srobert         form_delegate_sp->GetName().c_str(), bounds, true);
3560*f6aab3d8Srobert     WindowDelegateSP window_delegate_sp =
3561*f6aab3d8Srobert         WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
3562*f6aab3d8Srobert     form_window_sp->SetDelegate(window_delegate_sp);
3563*f6aab3d8Srobert 
3564*f6aab3d8Srobert     return true;
3565*f6aab3d8Srobert   }
3566*f6aab3d8Srobert 
GetTarget()3567*f6aab3d8Srobert   Target *GetTarget() {
3568*f6aab3d8Srobert     Target *target = m_debugger.GetSelectedTarget().get();
3569*f6aab3d8Srobert 
3570*f6aab3d8Srobert     if (target == nullptr) {
3571*f6aab3d8Srobert       SetError("No target exists!");
3572*f6aab3d8Srobert       return nullptr;
3573*f6aab3d8Srobert     }
3574*f6aab3d8Srobert 
3575*f6aab3d8Srobert     ModuleSP exe_module_sp = target->GetExecutableModule();
3576*f6aab3d8Srobert 
3577*f6aab3d8Srobert     if (exe_module_sp == nullptr) {
3578*f6aab3d8Srobert       SetError("No executable in target!");
3579*f6aab3d8Srobert       return nullptr;
3580*f6aab3d8Srobert     }
3581*f6aab3d8Srobert 
3582*f6aab3d8Srobert     return target;
3583*f6aab3d8Srobert   }
3584*f6aab3d8Srobert 
Launch(Window & window)3585*f6aab3d8Srobert   void Launch(Window &window) {
3586*f6aab3d8Srobert     ClearError();
3587*f6aab3d8Srobert 
3588*f6aab3d8Srobert     bool all_fields_are_valid = CheckFieldsValidity();
3589*f6aab3d8Srobert     if (!all_fields_are_valid)
3590*f6aab3d8Srobert       return;
3591*f6aab3d8Srobert 
3592*f6aab3d8Srobert     bool process_is_running = StopRunningProcess();
3593*f6aab3d8Srobert     if (process_is_running)
3594*f6aab3d8Srobert       return;
3595*f6aab3d8Srobert 
3596*f6aab3d8Srobert     Target *target = GetTarget();
3597*f6aab3d8Srobert     if (HasError())
3598*f6aab3d8Srobert       return;
3599*f6aab3d8Srobert 
3600*f6aab3d8Srobert     StreamString stream;
3601*f6aab3d8Srobert     ProcessLaunchInfo launch_info = GetLaunchInfo();
3602*f6aab3d8Srobert     Status status = target->Launch(launch_info, &stream);
3603*f6aab3d8Srobert 
3604*f6aab3d8Srobert     if (status.Fail()) {
3605*f6aab3d8Srobert       SetError(status.AsCString());
3606*f6aab3d8Srobert       return;
3607*f6aab3d8Srobert     }
3608*f6aab3d8Srobert 
3609*f6aab3d8Srobert     ProcessSP process_sp(target->GetProcessSP());
3610*f6aab3d8Srobert     if (!process_sp) {
3611*f6aab3d8Srobert       SetError("Launched successfully but target has no process!");
3612*f6aab3d8Srobert       return;
3613*f6aab3d8Srobert     }
3614*f6aab3d8Srobert 
3615*f6aab3d8Srobert     window.GetParent()->RemoveSubWindow(&window);
3616*f6aab3d8Srobert   }
3617*f6aab3d8Srobert 
3618*f6aab3d8Srobert protected:
3619*f6aab3d8Srobert   Debugger &m_debugger;
3620*f6aab3d8Srobert   WindowSP m_main_window_sp;
3621*f6aab3d8Srobert 
3622*f6aab3d8Srobert   ArgumentsFieldDelegate *m_arguments_field;
3623*f6aab3d8Srobert   EnvironmentVariableListFieldDelegate *m_target_environment_field;
3624*f6aab3d8Srobert   DirectoryFieldDelegate *m_working_directory_field;
3625*f6aab3d8Srobert 
3626*f6aab3d8Srobert   BooleanFieldDelegate *m_show_advanced_field;
3627*f6aab3d8Srobert 
3628*f6aab3d8Srobert   BooleanFieldDelegate *m_stop_at_entry_field;
3629*f6aab3d8Srobert   BooleanFieldDelegate *m_detach_on_error_field;
3630*f6aab3d8Srobert   BooleanFieldDelegate *m_disable_aslr_field;
3631*f6aab3d8Srobert   ProcessPluginFieldDelegate *m_plugin_field;
3632*f6aab3d8Srobert   ArchFieldDelegate *m_arch_field;
3633*f6aab3d8Srobert   FileFieldDelegate *m_shell_field;
3634*f6aab3d8Srobert   BooleanFieldDelegate *m_expand_shell_arguments_field;
3635*f6aab3d8Srobert   BooleanFieldDelegate *m_disable_standard_io_field;
3636*f6aab3d8Srobert   FileFieldDelegate *m_standard_input_field;
3637*f6aab3d8Srobert   FileFieldDelegate *m_standard_output_field;
3638*f6aab3d8Srobert   FileFieldDelegate *m_standard_error_field;
3639*f6aab3d8Srobert 
3640*f6aab3d8Srobert   BooleanFieldDelegate *m_show_inherited_environment_field;
3641*f6aab3d8Srobert   EnvironmentVariableListFieldDelegate *m_inherited_environment_field;
3642*f6aab3d8Srobert };
3643*f6aab3d8Srobert 
3644*f6aab3d8Srobert ////////////
3645*f6aab3d8Srobert // Searchers
3646*f6aab3d8Srobert ////////////
3647*f6aab3d8Srobert 
3648*f6aab3d8Srobert class SearcherDelegate {
3649*f6aab3d8Srobert public:
3650*f6aab3d8Srobert   SearcherDelegate() = default;
3651*f6aab3d8Srobert 
3652*f6aab3d8Srobert   virtual ~SearcherDelegate() = default;
3653*f6aab3d8Srobert 
3654*f6aab3d8Srobert   virtual int GetNumberOfMatches() = 0;
3655*f6aab3d8Srobert 
3656*f6aab3d8Srobert   // Get the string that will be displayed for the match at the input index.
3657*f6aab3d8Srobert   virtual const std::string &GetMatchTextAtIndex(int index) = 0;
3658*f6aab3d8Srobert 
3659*f6aab3d8Srobert   // Update the matches of the search. This is executed every time the text
3660*f6aab3d8Srobert   // field handles an event.
3661*f6aab3d8Srobert   virtual void UpdateMatches(const std::string &text) = 0;
3662*f6aab3d8Srobert 
3663*f6aab3d8Srobert   // Execute the user callback given the index of some match. This is executed
3664*f6aab3d8Srobert   // once the user selects a match.
3665*f6aab3d8Srobert   virtual void ExecuteCallback(int match_index) = 0;
3666*f6aab3d8Srobert };
3667*f6aab3d8Srobert 
3668*f6aab3d8Srobert typedef std::shared_ptr<SearcherDelegate> SearcherDelegateSP;
3669*f6aab3d8Srobert 
3670*f6aab3d8Srobert class SearcherWindowDelegate : public WindowDelegate {
3671*f6aab3d8Srobert public:
SearcherWindowDelegate(SearcherDelegateSP & delegate_sp)3672*f6aab3d8Srobert   SearcherWindowDelegate(SearcherDelegateSP &delegate_sp)
3673*f6aab3d8Srobert       : m_delegate_sp(delegate_sp), m_text_field("Search", "", false) {
3674*f6aab3d8Srobert     ;
3675*f6aab3d8Srobert   }
3676*f6aab3d8Srobert 
3677*f6aab3d8Srobert   // A completion window is padded by one character from all sides. A text field
3678*f6aab3d8Srobert   // is first drawn for inputting the searcher request, then a list of matches
3679*f6aab3d8Srobert   // are displayed in a scrollable list.
3680*f6aab3d8Srobert   //
3681*f6aab3d8Srobert   // ___<Searcher Window Name>____________________________
3682*f6aab3d8Srobert   // |                                                   |
3683*f6aab3d8Srobert   // | __[Search]_______________________________________ |
3684*f6aab3d8Srobert   // | |                                               | |
3685*f6aab3d8Srobert   // | |_______________________________________________| |
3686*f6aab3d8Srobert   // | - Match 1.                                        |
3687*f6aab3d8Srobert   // | - Match 2.                                        |
3688*f6aab3d8Srobert   // | - ...                                             |
3689*f6aab3d8Srobert   // |                                                   |
3690*f6aab3d8Srobert   // |____________________________[Press Esc to Cancel]__|
3691*f6aab3d8Srobert   //
3692*f6aab3d8Srobert 
3693*f6aab3d8Srobert   // Get the index of the last visible match. Assuming at least one match
3694*f6aab3d8Srobert   // exists.
GetLastVisibleMatch(int height)3695*f6aab3d8Srobert   int GetLastVisibleMatch(int height) {
3696*f6aab3d8Srobert     int index = m_first_visible_match + height;
3697*f6aab3d8Srobert     return std::min(index, m_delegate_sp->GetNumberOfMatches()) - 1;
3698*f6aab3d8Srobert   }
3699*f6aab3d8Srobert 
GetNumberOfVisibleMatches(int height)3700*f6aab3d8Srobert   int GetNumberOfVisibleMatches(int height) {
3701*f6aab3d8Srobert     return GetLastVisibleMatch(height) - m_first_visible_match + 1;
3702*f6aab3d8Srobert   }
3703*f6aab3d8Srobert 
UpdateScrolling(Surface & surface)3704*f6aab3d8Srobert   void UpdateScrolling(Surface &surface) {
3705*f6aab3d8Srobert     if (m_selected_match < m_first_visible_match) {
3706*f6aab3d8Srobert       m_first_visible_match = m_selected_match;
3707*f6aab3d8Srobert       return;
3708*f6aab3d8Srobert     }
3709*f6aab3d8Srobert 
3710*f6aab3d8Srobert     int height = surface.GetHeight();
3711*f6aab3d8Srobert     int last_visible_match = GetLastVisibleMatch(height);
3712*f6aab3d8Srobert     if (m_selected_match > last_visible_match) {
3713*f6aab3d8Srobert       m_first_visible_match = m_selected_match - height + 1;
3714*f6aab3d8Srobert     }
3715*f6aab3d8Srobert   }
3716*f6aab3d8Srobert 
DrawMatches(Surface & surface)3717*f6aab3d8Srobert   void DrawMatches(Surface &surface) {
3718*f6aab3d8Srobert     if (m_delegate_sp->GetNumberOfMatches() == 0)
3719*f6aab3d8Srobert       return;
3720*f6aab3d8Srobert 
3721*f6aab3d8Srobert     UpdateScrolling(surface);
3722*f6aab3d8Srobert 
3723*f6aab3d8Srobert     int count = GetNumberOfVisibleMatches(surface.GetHeight());
3724*f6aab3d8Srobert     for (int i = 0; i < count; i++) {
3725*f6aab3d8Srobert       surface.MoveCursor(1, i);
3726*f6aab3d8Srobert       int current_match = m_first_visible_match + i;
3727*f6aab3d8Srobert       if (current_match == m_selected_match)
3728*f6aab3d8Srobert         surface.AttributeOn(A_REVERSE);
3729*f6aab3d8Srobert       surface.PutCString(
3730*f6aab3d8Srobert           m_delegate_sp->GetMatchTextAtIndex(current_match).c_str());
3731*f6aab3d8Srobert       if (current_match == m_selected_match)
3732*f6aab3d8Srobert         surface.AttributeOff(A_REVERSE);
3733*f6aab3d8Srobert     }
3734*f6aab3d8Srobert   }
3735*f6aab3d8Srobert 
DrawContent(Surface & surface)3736*f6aab3d8Srobert   void DrawContent(Surface &surface) {
3737*f6aab3d8Srobert     Rect content_bounds = surface.GetFrame();
3738*f6aab3d8Srobert     Rect text_field_bounds, matchs_bounds;
3739*f6aab3d8Srobert     content_bounds.HorizontalSplit(m_text_field.FieldDelegateGetHeight(),
3740*f6aab3d8Srobert                                    text_field_bounds, matchs_bounds);
3741*f6aab3d8Srobert     Surface text_field_surface = surface.SubSurface(text_field_bounds);
3742*f6aab3d8Srobert     Surface matches_surface = surface.SubSurface(matchs_bounds);
3743*f6aab3d8Srobert 
3744*f6aab3d8Srobert     m_text_field.FieldDelegateDraw(text_field_surface, true);
3745*f6aab3d8Srobert     DrawMatches(matches_surface);
3746*f6aab3d8Srobert   }
3747*f6aab3d8Srobert 
WindowDelegateDraw(Window & window,bool force)3748*f6aab3d8Srobert   bool WindowDelegateDraw(Window &window, bool force) override {
3749*f6aab3d8Srobert     window.Erase();
3750*f6aab3d8Srobert 
3751*f6aab3d8Srobert     window.DrawTitleBox(window.GetName(), "Press Esc to Cancel");
3752*f6aab3d8Srobert 
3753*f6aab3d8Srobert     Rect content_bounds = window.GetFrame();
3754*f6aab3d8Srobert     content_bounds.Inset(2, 2);
3755*f6aab3d8Srobert     Surface content_surface = window.SubSurface(content_bounds);
3756*f6aab3d8Srobert 
3757*f6aab3d8Srobert     DrawContent(content_surface);
3758*f6aab3d8Srobert     return true;
3759*f6aab3d8Srobert   }
3760*f6aab3d8Srobert 
SelectNext()3761*f6aab3d8Srobert   void SelectNext() {
3762*f6aab3d8Srobert     if (m_selected_match != m_delegate_sp->GetNumberOfMatches() - 1)
3763*f6aab3d8Srobert       m_selected_match++;
3764*f6aab3d8Srobert   }
3765*f6aab3d8Srobert 
SelectPrevious()3766*f6aab3d8Srobert   void SelectPrevious() {
3767*f6aab3d8Srobert     if (m_selected_match != 0)
3768*f6aab3d8Srobert       m_selected_match--;
3769*f6aab3d8Srobert   }
3770*f6aab3d8Srobert 
ExecuteCallback(Window & window)3771*f6aab3d8Srobert   void ExecuteCallback(Window &window) {
3772*f6aab3d8Srobert     m_delegate_sp->ExecuteCallback(m_selected_match);
3773*f6aab3d8Srobert     window.GetParent()->RemoveSubWindow(&window);
3774*f6aab3d8Srobert   }
3775*f6aab3d8Srobert 
UpdateMatches()3776*f6aab3d8Srobert   void UpdateMatches() {
3777*f6aab3d8Srobert     m_delegate_sp->UpdateMatches(m_text_field.GetText());
3778*f6aab3d8Srobert     m_selected_match = 0;
3779*f6aab3d8Srobert   }
3780*f6aab3d8Srobert 
WindowDelegateHandleChar(Window & window,int key)3781*f6aab3d8Srobert   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
3782*f6aab3d8Srobert     switch (key) {
3783*f6aab3d8Srobert     case '\r':
3784*f6aab3d8Srobert     case '\n':
3785*f6aab3d8Srobert     case KEY_ENTER:
3786*f6aab3d8Srobert       ExecuteCallback(window);
3787*f6aab3d8Srobert       return eKeyHandled;
3788*f6aab3d8Srobert     case '\t':
3789*f6aab3d8Srobert     case KEY_DOWN:
3790*f6aab3d8Srobert       SelectNext();
3791*f6aab3d8Srobert       return eKeyHandled;
3792*f6aab3d8Srobert     case KEY_SHIFT_TAB:
3793*f6aab3d8Srobert     case KEY_UP:
3794*f6aab3d8Srobert       SelectPrevious();
3795*f6aab3d8Srobert       return eKeyHandled;
3796*f6aab3d8Srobert     case KEY_ESCAPE:
3797*f6aab3d8Srobert       window.GetParent()->RemoveSubWindow(&window);
3798*f6aab3d8Srobert       return eKeyHandled;
3799*f6aab3d8Srobert     default:
3800*f6aab3d8Srobert       break;
3801*f6aab3d8Srobert     }
3802*f6aab3d8Srobert 
3803*f6aab3d8Srobert     if (m_text_field.FieldDelegateHandleChar(key) == eKeyHandled)
3804*f6aab3d8Srobert       UpdateMatches();
3805*f6aab3d8Srobert 
3806*f6aab3d8Srobert     return eKeyHandled;
3807*f6aab3d8Srobert   }
3808*f6aab3d8Srobert 
3809*f6aab3d8Srobert protected:
3810*f6aab3d8Srobert   SearcherDelegateSP m_delegate_sp;
3811*f6aab3d8Srobert   TextFieldDelegate m_text_field;
3812*f6aab3d8Srobert   // The index of the currently selected match.
3813*f6aab3d8Srobert   int m_selected_match = 0;
3814*f6aab3d8Srobert   // The index of the first visible match.
3815*f6aab3d8Srobert   int m_first_visible_match = 0;
3816*f6aab3d8Srobert };
3817*f6aab3d8Srobert 
3818*f6aab3d8Srobert //////////////////////////////
3819*f6aab3d8Srobert // Searcher Delegate Instances
3820*f6aab3d8Srobert //////////////////////////////
3821*f6aab3d8Srobert 
3822*f6aab3d8Srobert // This is a searcher delegate wrapper around CommandCompletions common
3823*f6aab3d8Srobert // callbacks. The callbacks are only given the match string. The completion_mask
3824*f6aab3d8Srobert // can be a combination of CommonCompletionTypes.
3825*f6aab3d8Srobert class CommonCompletionSearcherDelegate : public SearcherDelegate {
3826*f6aab3d8Srobert public:
3827*f6aab3d8Srobert   typedef std::function<void(const std::string &)> CallbackType;
3828*f6aab3d8Srobert 
CommonCompletionSearcherDelegate(Debugger & debugger,uint32_t completion_mask,CallbackType callback)3829*f6aab3d8Srobert   CommonCompletionSearcherDelegate(Debugger &debugger, uint32_t completion_mask,
3830*f6aab3d8Srobert                                    CallbackType callback)
3831*f6aab3d8Srobert       : m_debugger(debugger), m_completion_mask(completion_mask),
3832*f6aab3d8Srobert         m_callback(callback) {}
3833*f6aab3d8Srobert 
GetNumberOfMatches()3834*f6aab3d8Srobert   int GetNumberOfMatches() override { return m_matches.GetSize(); }
3835*f6aab3d8Srobert 
GetMatchTextAtIndex(int index)3836*f6aab3d8Srobert   const std::string &GetMatchTextAtIndex(int index) override {
3837*f6aab3d8Srobert     return m_matches[index];
3838*f6aab3d8Srobert   }
3839*f6aab3d8Srobert 
UpdateMatches(const std::string & text)3840*f6aab3d8Srobert   void UpdateMatches(const std::string &text) override {
3841*f6aab3d8Srobert     CompletionResult result;
3842*f6aab3d8Srobert     CompletionRequest request(text.c_str(), text.size(), result);
3843*f6aab3d8Srobert     CommandCompletions::InvokeCommonCompletionCallbacks(
3844*f6aab3d8Srobert         m_debugger.GetCommandInterpreter(), m_completion_mask, request,
3845*f6aab3d8Srobert         nullptr);
3846*f6aab3d8Srobert     result.GetMatches(m_matches);
3847*f6aab3d8Srobert   }
3848*f6aab3d8Srobert 
ExecuteCallback(int match_index)3849*f6aab3d8Srobert   void ExecuteCallback(int match_index) override {
3850*f6aab3d8Srobert     m_callback(m_matches[match_index]);
3851*f6aab3d8Srobert   }
3852*f6aab3d8Srobert 
3853*f6aab3d8Srobert protected:
3854*f6aab3d8Srobert   Debugger &m_debugger;
3855*f6aab3d8Srobert   // A compound mask from CommonCompletionTypes.
3856*f6aab3d8Srobert   uint32_t m_completion_mask;
3857*f6aab3d8Srobert   // A callback to execute once the user selects a match. The match is passed to
3858*f6aab3d8Srobert   // the callback as a string.
3859*f6aab3d8Srobert   CallbackType m_callback;
3860*f6aab3d8Srobert   StringList m_matches;
3861*f6aab3d8Srobert };
3862*f6aab3d8Srobert 
3863*f6aab3d8Srobert ////////
3864*f6aab3d8Srobert // Menus
3865*f6aab3d8Srobert ////////
3866*f6aab3d8Srobert 
3867061da546Spatrick class MenuDelegate {
3868061da546Spatrick public:
3869061da546Spatrick   virtual ~MenuDelegate() = default;
3870061da546Spatrick 
3871061da546Spatrick   virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0;
3872061da546Spatrick };
3873061da546Spatrick 
3874061da546Spatrick class Menu : public WindowDelegate {
3875061da546Spatrick public:
3876061da546Spatrick   enum class Type { Invalid, Bar, Item, Separator };
3877061da546Spatrick 
3878061da546Spatrick   // Menubar or separator constructor
3879061da546Spatrick   Menu(Type type);
3880061da546Spatrick 
3881061da546Spatrick   // Menuitem constructor
3882061da546Spatrick   Menu(const char *name, const char *key_name, int key_value,
3883061da546Spatrick        uint64_t identifier);
3884061da546Spatrick 
3885061da546Spatrick   ~Menu() override = default;
3886061da546Spatrick 
GetDelegate() const3887061da546Spatrick   const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; }
3888061da546Spatrick 
SetDelegate(const MenuDelegateSP & delegate_sp)3889061da546Spatrick   void SetDelegate(const MenuDelegateSP &delegate_sp) {
3890061da546Spatrick     m_delegate_sp = delegate_sp;
3891061da546Spatrick   }
3892061da546Spatrick 
3893061da546Spatrick   void RecalculateNameLengths();
3894061da546Spatrick 
3895061da546Spatrick   void AddSubmenu(const MenuSP &menu_sp);
3896061da546Spatrick 
3897061da546Spatrick   int DrawAndRunMenu(Window &window);
3898061da546Spatrick 
3899061da546Spatrick   void DrawMenuTitle(Window &window, bool highlight);
3900061da546Spatrick 
3901061da546Spatrick   bool WindowDelegateDraw(Window &window, bool force) override;
3902061da546Spatrick 
3903061da546Spatrick   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
3904061da546Spatrick 
ActionPrivate(Menu & menu)3905061da546Spatrick   MenuActionResult ActionPrivate(Menu &menu) {
3906061da546Spatrick     MenuActionResult result = MenuActionResult::NotHandled;
3907061da546Spatrick     if (m_delegate_sp) {
3908061da546Spatrick       result = m_delegate_sp->MenuDelegateAction(menu);
3909061da546Spatrick       if (result != MenuActionResult::NotHandled)
3910061da546Spatrick         return result;
3911061da546Spatrick     } else if (m_parent) {
3912061da546Spatrick       result = m_parent->ActionPrivate(menu);
3913061da546Spatrick       if (result != MenuActionResult::NotHandled)
3914061da546Spatrick         return result;
3915061da546Spatrick     }
3916061da546Spatrick     return m_canned_result;
3917061da546Spatrick   }
3918061da546Spatrick 
Action()3919061da546Spatrick   MenuActionResult Action() {
3920061da546Spatrick     // Call the recursive action so it can try to handle it with the menu
3921061da546Spatrick     // delegate, and if not, try our parent menu
3922061da546Spatrick     return ActionPrivate(*this);
3923061da546Spatrick   }
3924061da546Spatrick 
SetCannedResult(MenuActionResult result)3925061da546Spatrick   void SetCannedResult(MenuActionResult result) { m_canned_result = result; }
3926061da546Spatrick 
GetSubmenus()3927061da546Spatrick   Menus &GetSubmenus() { return m_submenus; }
3928061da546Spatrick 
GetSubmenus() const3929061da546Spatrick   const Menus &GetSubmenus() const { return m_submenus; }
3930061da546Spatrick 
GetSelectedSubmenuIndex() const3931061da546Spatrick   int GetSelectedSubmenuIndex() const { return m_selected; }
3932061da546Spatrick 
SetSelectedSubmenuIndex(int idx)3933061da546Spatrick   void SetSelectedSubmenuIndex(int idx) { m_selected = idx; }
3934061da546Spatrick 
GetType() const3935061da546Spatrick   Type GetType() const { return m_type; }
3936061da546Spatrick 
GetStartingColumn() const3937061da546Spatrick   int GetStartingColumn() const { return m_start_col; }
3938061da546Spatrick 
SetStartingColumn(int col)3939061da546Spatrick   void SetStartingColumn(int col) { m_start_col = col; }
3940061da546Spatrick 
GetKeyValue() const3941061da546Spatrick   int GetKeyValue() const { return m_key_value; }
3942061da546Spatrick 
GetName()3943061da546Spatrick   std::string &GetName() { return m_name; }
3944061da546Spatrick 
GetDrawWidth() const3945061da546Spatrick   int GetDrawWidth() const {
3946061da546Spatrick     return m_max_submenu_name_length + m_max_submenu_key_name_length + 8;
3947061da546Spatrick   }
3948061da546Spatrick 
GetIdentifier() const3949061da546Spatrick   uint64_t GetIdentifier() const { return m_identifier; }
3950061da546Spatrick 
SetIdentifier(uint64_t identifier)3951061da546Spatrick   void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
3952061da546Spatrick 
3953061da546Spatrick protected:
3954061da546Spatrick   std::string m_name;
3955061da546Spatrick   std::string m_key_name;
3956061da546Spatrick   uint64_t m_identifier;
3957061da546Spatrick   Type m_type;
3958061da546Spatrick   int m_key_value;
3959061da546Spatrick   int m_start_col;
3960061da546Spatrick   int m_max_submenu_name_length;
3961061da546Spatrick   int m_max_submenu_key_name_length;
3962061da546Spatrick   int m_selected;
3963061da546Spatrick   Menu *m_parent;
3964061da546Spatrick   Menus m_submenus;
3965061da546Spatrick   WindowSP m_menu_window_sp;
3966061da546Spatrick   MenuActionResult m_canned_result;
3967061da546Spatrick   MenuDelegateSP m_delegate_sp;
3968061da546Spatrick };
3969061da546Spatrick 
3970061da546Spatrick // Menubar or separator constructor
Menu(Type type)3971061da546Spatrick Menu::Menu(Type type)
3972061da546Spatrick     : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0),
3973061da546Spatrick       m_start_col(0), m_max_submenu_name_length(0),
3974061da546Spatrick       m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
3975061da546Spatrick       m_submenus(), m_canned_result(MenuActionResult::NotHandled),
3976061da546Spatrick       m_delegate_sp() {}
3977061da546Spatrick 
3978061da546Spatrick // Menuitem constructor
Menu(const char * name,const char * key_name,int key_value,uint64_t identifier)3979061da546Spatrick Menu::Menu(const char *name, const char *key_name, int key_value,
3980061da546Spatrick            uint64_t identifier)
3981061da546Spatrick     : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid),
3982061da546Spatrick       m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0),
3983061da546Spatrick       m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
3984061da546Spatrick       m_submenus(), m_canned_result(MenuActionResult::NotHandled),
3985061da546Spatrick       m_delegate_sp() {
3986061da546Spatrick   if (name && name[0]) {
3987061da546Spatrick     m_name = name;
3988061da546Spatrick     m_type = Type::Item;
3989061da546Spatrick     if (key_name && key_name[0])
3990061da546Spatrick       m_key_name = key_name;
3991061da546Spatrick   } else {
3992061da546Spatrick     m_type = Type::Separator;
3993061da546Spatrick   }
3994061da546Spatrick }
3995061da546Spatrick 
RecalculateNameLengths()3996061da546Spatrick void Menu::RecalculateNameLengths() {
3997061da546Spatrick   m_max_submenu_name_length = 0;
3998061da546Spatrick   m_max_submenu_key_name_length = 0;
3999061da546Spatrick   Menus &submenus = GetSubmenus();
4000061da546Spatrick   const size_t num_submenus = submenus.size();
4001061da546Spatrick   for (size_t i = 0; i < num_submenus; ++i) {
4002061da546Spatrick     Menu *submenu = submenus[i].get();
4003061da546Spatrick     if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size())
4004061da546Spatrick       m_max_submenu_name_length = submenu->m_name.size();
4005061da546Spatrick     if (static_cast<size_t>(m_max_submenu_key_name_length) <
4006061da546Spatrick         submenu->m_key_name.size())
4007061da546Spatrick       m_max_submenu_key_name_length = submenu->m_key_name.size();
4008061da546Spatrick   }
4009061da546Spatrick }
4010061da546Spatrick 
AddSubmenu(const MenuSP & menu_sp)4011061da546Spatrick void Menu::AddSubmenu(const MenuSP &menu_sp) {
4012061da546Spatrick   menu_sp->m_parent = this;
4013061da546Spatrick   if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size())
4014061da546Spatrick     m_max_submenu_name_length = menu_sp->m_name.size();
4015061da546Spatrick   if (static_cast<size_t>(m_max_submenu_key_name_length) <
4016061da546Spatrick       menu_sp->m_key_name.size())
4017061da546Spatrick     m_max_submenu_key_name_length = menu_sp->m_key_name.size();
4018061da546Spatrick   m_submenus.push_back(menu_sp);
4019061da546Spatrick }
4020061da546Spatrick 
DrawMenuTitle(Window & window,bool highlight)4021061da546Spatrick void Menu::DrawMenuTitle(Window &window, bool highlight) {
4022061da546Spatrick   if (m_type == Type::Separator) {
4023061da546Spatrick     window.MoveCursor(0, window.GetCursorY());
4024061da546Spatrick     window.PutChar(ACS_LTEE);
4025061da546Spatrick     int width = window.GetWidth();
4026061da546Spatrick     if (width > 2) {
4027061da546Spatrick       width -= 2;
4028061da546Spatrick       for (int i = 0; i < width; ++i)
4029061da546Spatrick         window.PutChar(ACS_HLINE);
4030061da546Spatrick     }
4031061da546Spatrick     window.PutChar(ACS_RTEE);
4032061da546Spatrick   } else {
4033061da546Spatrick     const int shortcut_key = m_key_value;
4034061da546Spatrick     bool underlined_shortcut = false;
4035be691f3bSpatrick     const attr_t highlight_attr = A_REVERSE;
4036061da546Spatrick     if (highlight)
4037be691f3bSpatrick       window.AttributeOn(highlight_attr);
4038dda28197Spatrick     if (llvm::isPrint(shortcut_key)) {
4039061da546Spatrick       size_t lower_pos = m_name.find(tolower(shortcut_key));
4040061da546Spatrick       size_t upper_pos = m_name.find(toupper(shortcut_key));
4041061da546Spatrick       const char *name = m_name.c_str();
4042061da546Spatrick       size_t pos = std::min<size_t>(lower_pos, upper_pos);
4043061da546Spatrick       if (pos != std::string::npos) {
4044061da546Spatrick         underlined_shortcut = true;
4045061da546Spatrick         if (pos > 0) {
4046061da546Spatrick           window.PutCString(name, pos);
4047061da546Spatrick           name += pos;
4048061da546Spatrick         }
4049061da546Spatrick         const attr_t shortcut_attr = A_UNDERLINE | A_BOLD;
4050061da546Spatrick         window.AttributeOn(shortcut_attr);
4051061da546Spatrick         window.PutChar(name[0]);
4052061da546Spatrick         window.AttributeOff(shortcut_attr);
4053061da546Spatrick         name++;
4054061da546Spatrick         if (name[0])
4055061da546Spatrick           window.PutCString(name);
4056061da546Spatrick       }
4057061da546Spatrick     }
4058061da546Spatrick 
4059061da546Spatrick     if (!underlined_shortcut) {
4060061da546Spatrick       window.PutCString(m_name.c_str());
4061061da546Spatrick     }
4062061da546Spatrick 
4063061da546Spatrick     if (highlight)
4064be691f3bSpatrick       window.AttributeOff(highlight_attr);
4065061da546Spatrick 
4066061da546Spatrick     if (m_key_name.empty()) {
4067dda28197Spatrick       if (!underlined_shortcut && llvm::isPrint(m_key_value)) {
4068be691f3bSpatrick         window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
4069061da546Spatrick         window.Printf(" (%c)", m_key_value);
4070be691f3bSpatrick         window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
4071061da546Spatrick       }
4072061da546Spatrick     } else {
4073be691f3bSpatrick       window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
4074061da546Spatrick       window.Printf(" (%s)", m_key_name.c_str());
4075be691f3bSpatrick       window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
4076061da546Spatrick     }
4077061da546Spatrick   }
4078061da546Spatrick }
4079061da546Spatrick 
WindowDelegateDraw(Window & window,bool force)4080061da546Spatrick bool Menu::WindowDelegateDraw(Window &window, bool force) {
4081061da546Spatrick   Menus &submenus = GetSubmenus();
4082061da546Spatrick   const size_t num_submenus = submenus.size();
4083061da546Spatrick   const int selected_idx = GetSelectedSubmenuIndex();
4084061da546Spatrick   Menu::Type menu_type = GetType();
4085061da546Spatrick   switch (menu_type) {
4086061da546Spatrick   case Menu::Type::Bar: {
4087be691f3bSpatrick     window.SetBackground(BlackOnWhite);
4088061da546Spatrick     window.MoveCursor(0, 0);
4089061da546Spatrick     for (size_t i = 0; i < num_submenus; ++i) {
4090061da546Spatrick       Menu *menu = submenus[i].get();
4091061da546Spatrick       if (i > 0)
4092061da546Spatrick         window.PutChar(' ');
4093061da546Spatrick       menu->SetStartingColumn(window.GetCursorX());
4094061da546Spatrick       window.PutCString("| ");
4095061da546Spatrick       menu->DrawMenuTitle(window, false);
4096061da546Spatrick     }
4097061da546Spatrick     window.PutCString(" |");
4098061da546Spatrick   } break;
4099061da546Spatrick 
4100061da546Spatrick   case Menu::Type::Item: {
4101061da546Spatrick     int y = 1;
4102061da546Spatrick     int x = 3;
4103061da546Spatrick     // Draw the menu
4104061da546Spatrick     int cursor_x = 0;
4105061da546Spatrick     int cursor_y = 0;
4106061da546Spatrick     window.Erase();
4107be691f3bSpatrick     window.SetBackground(BlackOnWhite);
4108061da546Spatrick     window.Box();
4109061da546Spatrick     for (size_t i = 0; i < num_submenus; ++i) {
4110061da546Spatrick       const bool is_selected = (i == static_cast<size_t>(selected_idx));
4111061da546Spatrick       window.MoveCursor(x, y + i);
4112061da546Spatrick       if (is_selected) {
4113061da546Spatrick         // Remember where we want the cursor to be
4114061da546Spatrick         cursor_x = x - 1;
4115061da546Spatrick         cursor_y = y + i;
4116061da546Spatrick       }
4117061da546Spatrick       submenus[i]->DrawMenuTitle(window, is_selected);
4118061da546Spatrick     }
4119061da546Spatrick     window.MoveCursor(cursor_x, cursor_y);
4120061da546Spatrick   } break;
4121061da546Spatrick 
4122061da546Spatrick   default:
4123061da546Spatrick   case Menu::Type::Separator:
4124061da546Spatrick     break;
4125061da546Spatrick   }
4126061da546Spatrick   return true; // Drawing handled...
4127061da546Spatrick }
4128061da546Spatrick 
WindowDelegateHandleChar(Window & window,int key)4129061da546Spatrick HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) {
4130061da546Spatrick   HandleCharResult result = eKeyNotHandled;
4131061da546Spatrick 
4132061da546Spatrick   Menus &submenus = GetSubmenus();
4133061da546Spatrick   const size_t num_submenus = submenus.size();
4134061da546Spatrick   const int selected_idx = GetSelectedSubmenuIndex();
4135061da546Spatrick   Menu::Type menu_type = GetType();
4136061da546Spatrick   if (menu_type == Menu::Type::Bar) {
4137061da546Spatrick     MenuSP run_menu_sp;
4138061da546Spatrick     switch (key) {
4139061da546Spatrick     case KEY_DOWN:
4140061da546Spatrick     case KEY_UP:
4141061da546Spatrick       // Show last menu or first menu
4142061da546Spatrick       if (selected_idx < static_cast<int>(num_submenus))
4143061da546Spatrick         run_menu_sp = submenus[selected_idx];
4144061da546Spatrick       else if (!submenus.empty())
4145061da546Spatrick         run_menu_sp = submenus.front();
4146061da546Spatrick       result = eKeyHandled;
4147061da546Spatrick       break;
4148061da546Spatrick 
4149061da546Spatrick     case KEY_RIGHT:
4150061da546Spatrick       ++m_selected;
4151061da546Spatrick       if (m_selected >= static_cast<int>(num_submenus))
4152061da546Spatrick         m_selected = 0;
4153061da546Spatrick       if (m_selected < static_cast<int>(num_submenus))
4154061da546Spatrick         run_menu_sp = submenus[m_selected];
4155061da546Spatrick       else if (!submenus.empty())
4156061da546Spatrick         run_menu_sp = submenus.front();
4157061da546Spatrick       result = eKeyHandled;
4158061da546Spatrick       break;
4159061da546Spatrick 
4160061da546Spatrick     case KEY_LEFT:
4161061da546Spatrick       --m_selected;
4162061da546Spatrick       if (m_selected < 0)
4163061da546Spatrick         m_selected = num_submenus - 1;
4164061da546Spatrick       if (m_selected < static_cast<int>(num_submenus))
4165061da546Spatrick         run_menu_sp = submenus[m_selected];
4166061da546Spatrick       else if (!submenus.empty())
4167061da546Spatrick         run_menu_sp = submenus.front();
4168061da546Spatrick       result = eKeyHandled;
4169061da546Spatrick       break;
4170061da546Spatrick 
4171061da546Spatrick     default:
4172061da546Spatrick       for (size_t i = 0; i < num_submenus; ++i) {
4173061da546Spatrick         if (submenus[i]->GetKeyValue() == key) {
4174061da546Spatrick           SetSelectedSubmenuIndex(i);
4175061da546Spatrick           run_menu_sp = submenus[i];
4176061da546Spatrick           result = eKeyHandled;
4177061da546Spatrick           break;
4178061da546Spatrick         }
4179061da546Spatrick       }
4180061da546Spatrick       break;
4181061da546Spatrick     }
4182061da546Spatrick 
4183061da546Spatrick     if (run_menu_sp) {
4184061da546Spatrick       // Run the action on this menu in case we need to populate the menu with
4185061da546Spatrick       // dynamic content and also in case check marks, and any other menu
4186061da546Spatrick       // decorations need to be calculated
4187061da546Spatrick       if (run_menu_sp->Action() == MenuActionResult::Quit)
4188061da546Spatrick         return eQuitApplication;
4189061da546Spatrick 
4190061da546Spatrick       Rect menu_bounds;
4191061da546Spatrick       menu_bounds.origin.x = run_menu_sp->GetStartingColumn();
4192061da546Spatrick       menu_bounds.origin.y = 1;
4193061da546Spatrick       menu_bounds.size.width = run_menu_sp->GetDrawWidth();
4194061da546Spatrick       menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2;
4195061da546Spatrick       if (m_menu_window_sp)
4196061da546Spatrick         window.GetParent()->RemoveSubWindow(m_menu_window_sp.get());
4197061da546Spatrick 
4198061da546Spatrick       m_menu_window_sp = window.GetParent()->CreateSubWindow(
4199061da546Spatrick           run_menu_sp->GetName().c_str(), menu_bounds, true);
4200061da546Spatrick       m_menu_window_sp->SetDelegate(run_menu_sp);
4201061da546Spatrick     }
4202061da546Spatrick   } else if (menu_type == Menu::Type::Item) {
4203061da546Spatrick     switch (key) {
4204061da546Spatrick     case KEY_DOWN:
4205061da546Spatrick       if (m_submenus.size() > 1) {
4206061da546Spatrick         const int start_select = m_selected;
4207061da546Spatrick         while (++m_selected != start_select) {
4208061da546Spatrick           if (static_cast<size_t>(m_selected) >= num_submenus)
4209061da546Spatrick             m_selected = 0;
4210061da546Spatrick           if (m_submenus[m_selected]->GetType() == Type::Separator)
4211061da546Spatrick             continue;
4212061da546Spatrick           else
4213061da546Spatrick             break;
4214061da546Spatrick         }
4215061da546Spatrick         return eKeyHandled;
4216061da546Spatrick       }
4217061da546Spatrick       break;
4218061da546Spatrick 
4219061da546Spatrick     case KEY_UP:
4220061da546Spatrick       if (m_submenus.size() > 1) {
4221061da546Spatrick         const int start_select = m_selected;
4222061da546Spatrick         while (--m_selected != start_select) {
4223061da546Spatrick           if (m_selected < static_cast<int>(0))
4224061da546Spatrick             m_selected = num_submenus - 1;
4225061da546Spatrick           if (m_submenus[m_selected]->GetType() == Type::Separator)
4226061da546Spatrick             continue;
4227061da546Spatrick           else
4228061da546Spatrick             break;
4229061da546Spatrick         }
4230061da546Spatrick         return eKeyHandled;
4231061da546Spatrick       }
4232061da546Spatrick       break;
4233061da546Spatrick 
4234061da546Spatrick     case KEY_RETURN:
4235061da546Spatrick       if (static_cast<size_t>(selected_idx) < num_submenus) {
4236061da546Spatrick         if (submenus[selected_idx]->Action() == MenuActionResult::Quit)
4237061da546Spatrick           return eQuitApplication;
4238061da546Spatrick         window.GetParent()->RemoveSubWindow(&window);
4239061da546Spatrick         return eKeyHandled;
4240061da546Spatrick       }
4241061da546Spatrick       break;
4242061da546Spatrick 
4243061da546Spatrick     case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in
4244061da546Spatrick                      // case other chars are entered for escaped sequences
4245061da546Spatrick       window.GetParent()->RemoveSubWindow(&window);
4246061da546Spatrick       return eKeyHandled;
4247061da546Spatrick 
4248061da546Spatrick     default:
4249061da546Spatrick       for (size_t i = 0; i < num_submenus; ++i) {
4250061da546Spatrick         Menu *menu = submenus[i].get();
4251061da546Spatrick         if (menu->GetKeyValue() == key) {
4252061da546Spatrick           SetSelectedSubmenuIndex(i);
4253061da546Spatrick           window.GetParent()->RemoveSubWindow(&window);
4254061da546Spatrick           if (menu->Action() == MenuActionResult::Quit)
4255061da546Spatrick             return eQuitApplication;
4256061da546Spatrick           return eKeyHandled;
4257061da546Spatrick         }
4258061da546Spatrick       }
4259061da546Spatrick       break;
4260061da546Spatrick     }
4261061da546Spatrick   } else if (menu_type == Menu::Type::Separator) {
4262061da546Spatrick   }
4263061da546Spatrick   return result;
4264061da546Spatrick }
4265061da546Spatrick 
4266061da546Spatrick class Application {
4267061da546Spatrick public:
Application(FILE * in,FILE * out)4268*f6aab3d8Srobert   Application(FILE *in, FILE *out) : m_window_sp(), m_in(in), m_out(out) {}
4269061da546Spatrick 
~Application()4270061da546Spatrick   ~Application() {
4271061da546Spatrick     m_window_delegates.clear();
4272061da546Spatrick     m_window_sp.reset();
4273061da546Spatrick     if (m_screen) {
4274061da546Spatrick       ::delscreen(m_screen);
4275061da546Spatrick       m_screen = nullptr;
4276061da546Spatrick     }
4277061da546Spatrick   }
4278061da546Spatrick 
Initialize()4279061da546Spatrick   void Initialize() {
4280061da546Spatrick     m_screen = ::newterm(nullptr, m_out, m_in);
4281061da546Spatrick     ::start_color();
4282061da546Spatrick     ::curs_set(0);
4283061da546Spatrick     ::noecho();
4284061da546Spatrick     ::keypad(stdscr, TRUE);
4285061da546Spatrick   }
4286061da546Spatrick 
Terminate()4287061da546Spatrick   void Terminate() { ::endwin(); }
4288061da546Spatrick 
Run(Debugger & debugger)4289061da546Spatrick   void Run(Debugger &debugger) {
4290061da546Spatrick     bool done = false;
4291061da546Spatrick     int delay_in_tenths_of_a_second = 1;
4292061da546Spatrick 
4293*f6aab3d8Srobert     // Alas the threading model in curses is a bit lame so we need to resort
4294*f6aab3d8Srobert     // to polling every 0.5 seconds. We could poll for stdin ourselves and
4295*f6aab3d8Srobert     // then pass the keys down but then we need to translate all of the escape
4296061da546Spatrick     // sequences ourselves. So we resort to polling for input because we need
4297061da546Spatrick     // to receive async process events while in this loop.
4298061da546Spatrick 
4299*f6aab3d8Srobert     halfdelay(delay_in_tenths_of_a_second); // Poll using some number of
4300*f6aab3d8Srobert                                             // tenths of seconds seconds when
4301*f6aab3d8Srobert                                             // calling Window::GetChar()
4302061da546Spatrick 
4303061da546Spatrick     ListenerSP listener_sp(
4304061da546Spatrick         Listener::MakeListener("lldb.IOHandler.curses.Application"));
4305061da546Spatrick     ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass());
4306061da546Spatrick     debugger.EnableForwardEvents(listener_sp);
4307061da546Spatrick 
4308be691f3bSpatrick     m_update_screen = true;
4309061da546Spatrick #if defined(__APPLE__)
4310061da546Spatrick     std::deque<int> escape_chars;
4311061da546Spatrick #endif
4312061da546Spatrick 
4313061da546Spatrick     while (!done) {
4314be691f3bSpatrick       if (m_update_screen) {
4315061da546Spatrick         m_window_sp->Draw(false);
4316061da546Spatrick         // All windows should be calling Window::DeferredRefresh() instead of
4317061da546Spatrick         // Window::Refresh() so we can do a single update and avoid any screen
4318061da546Spatrick         // blinking
4319061da546Spatrick         update_panels();
4320061da546Spatrick 
4321061da546Spatrick         // Cursor hiding isn't working on MacOSX, so hide it in the top left
4322061da546Spatrick         // corner
4323061da546Spatrick         m_window_sp->MoveCursor(0, 0);
4324061da546Spatrick 
4325061da546Spatrick         doupdate();
4326be691f3bSpatrick         m_update_screen = false;
4327061da546Spatrick       }
4328061da546Spatrick 
4329061da546Spatrick #if defined(__APPLE__)
4330061da546Spatrick       // Terminal.app doesn't map its function keys correctly, F1-F4 default
4331061da546Spatrick       // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if
4332061da546Spatrick       // possible
4333061da546Spatrick       int ch;
4334061da546Spatrick       if (escape_chars.empty())
4335061da546Spatrick         ch = m_window_sp->GetChar();
4336061da546Spatrick       else {
4337061da546Spatrick         ch = escape_chars.front();
4338061da546Spatrick         escape_chars.pop_front();
4339061da546Spatrick       }
4340061da546Spatrick       if (ch == KEY_ESCAPE) {
4341061da546Spatrick         int ch2 = m_window_sp->GetChar();
4342061da546Spatrick         if (ch2 == 'O') {
4343061da546Spatrick           int ch3 = m_window_sp->GetChar();
4344061da546Spatrick           switch (ch3) {
4345061da546Spatrick           case 'P':
4346061da546Spatrick             ch = KEY_F(1);
4347061da546Spatrick             break;
4348061da546Spatrick           case 'Q':
4349061da546Spatrick             ch = KEY_F(2);
4350061da546Spatrick             break;
4351061da546Spatrick           case 'R':
4352061da546Spatrick             ch = KEY_F(3);
4353061da546Spatrick             break;
4354061da546Spatrick           case 'S':
4355061da546Spatrick             ch = KEY_F(4);
4356061da546Spatrick             break;
4357061da546Spatrick           default:
4358061da546Spatrick             escape_chars.push_back(ch2);
4359061da546Spatrick             if (ch3 != -1)
4360061da546Spatrick               escape_chars.push_back(ch3);
4361061da546Spatrick             break;
4362061da546Spatrick           }
4363061da546Spatrick         } else if (ch2 != -1)
4364061da546Spatrick           escape_chars.push_back(ch2);
4365061da546Spatrick       }
4366061da546Spatrick #else
4367061da546Spatrick       int ch = m_window_sp->GetChar();
4368061da546Spatrick 
4369061da546Spatrick #endif
4370061da546Spatrick       if (ch == -1) {
4371061da546Spatrick         if (feof(m_in) || ferror(m_in)) {
4372061da546Spatrick           done = true;
4373061da546Spatrick         } else {
4374061da546Spatrick           // Just a timeout from using halfdelay(), check for events
4375061da546Spatrick           EventSP event_sp;
4376061da546Spatrick           while (listener_sp->PeekAtNextEvent()) {
4377061da546Spatrick             listener_sp->GetEvent(event_sp, std::chrono::seconds(0));
4378061da546Spatrick 
4379061da546Spatrick             if (event_sp) {
4380061da546Spatrick               Broadcaster *broadcaster = event_sp->GetBroadcaster();
4381061da546Spatrick               if (broadcaster) {
4382061da546Spatrick                 // uint32_t event_type = event_sp->GetType();
4383061da546Spatrick                 ConstString broadcaster_class(
4384061da546Spatrick                     broadcaster->GetBroadcasterClass());
4385061da546Spatrick                 if (broadcaster_class == broadcaster_class_process) {
4386be691f3bSpatrick                   m_update_screen = true;
4387061da546Spatrick                   continue; // Don't get any key, just update our view
4388061da546Spatrick                 }
4389061da546Spatrick               }
4390061da546Spatrick             }
4391061da546Spatrick           }
4392061da546Spatrick         }
4393061da546Spatrick       } else {
4394061da546Spatrick         HandleCharResult key_result = m_window_sp->HandleChar(ch);
4395061da546Spatrick         switch (key_result) {
4396061da546Spatrick         case eKeyHandled:
4397be691f3bSpatrick           m_update_screen = true;
4398061da546Spatrick           break;
4399061da546Spatrick         case eKeyNotHandled:
4400be691f3bSpatrick           if (ch == 12) { // Ctrl+L, force full redraw
4401be691f3bSpatrick             redrawwin(m_window_sp->get());
4402be691f3bSpatrick             m_update_screen = true;
4403be691f3bSpatrick           }
4404061da546Spatrick           break;
4405061da546Spatrick         case eQuitApplication:
4406061da546Spatrick           done = true;
4407061da546Spatrick           break;
4408061da546Spatrick         }
4409061da546Spatrick       }
4410061da546Spatrick     }
4411061da546Spatrick 
4412061da546Spatrick     debugger.CancelForwardEvents(listener_sp);
4413061da546Spatrick   }
4414061da546Spatrick 
GetMainWindow()4415061da546Spatrick   WindowSP &GetMainWindow() {
4416061da546Spatrick     if (!m_window_sp)
4417061da546Spatrick       m_window_sp = std::make_shared<Window>("main", stdscr, false);
4418061da546Spatrick     return m_window_sp;
4419061da546Spatrick   }
4420061da546Spatrick 
TerminalSizeChanged()4421be691f3bSpatrick   void TerminalSizeChanged() {
4422be691f3bSpatrick     ::endwin();
4423be691f3bSpatrick     ::refresh();
4424be691f3bSpatrick     Rect content_bounds = m_window_sp->GetFrame();
4425be691f3bSpatrick     m_window_sp->SetBounds(content_bounds);
4426be691f3bSpatrick     if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar"))
4427be691f3bSpatrick       menubar_window_sp->SetBounds(content_bounds.MakeMenuBar());
4428be691f3bSpatrick     if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status"))
4429be691f3bSpatrick       status_window_sp->SetBounds(content_bounds.MakeStatusBar());
4430be691f3bSpatrick 
4431be691f3bSpatrick     WindowSP source_window_sp = m_window_sp->FindSubWindow("Source");
4432be691f3bSpatrick     WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables");
4433be691f3bSpatrick     WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers");
4434be691f3bSpatrick     WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads");
4435be691f3bSpatrick 
4436be691f3bSpatrick     Rect threads_bounds;
4437be691f3bSpatrick     Rect source_variables_bounds;
4438be691f3bSpatrick     content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
4439be691f3bSpatrick                                            threads_bounds);
4440be691f3bSpatrick     if (threads_window_sp)
4441be691f3bSpatrick       threads_window_sp->SetBounds(threads_bounds);
4442be691f3bSpatrick     else
4443be691f3bSpatrick       source_variables_bounds = content_bounds;
4444be691f3bSpatrick 
4445be691f3bSpatrick     Rect source_bounds;
4446be691f3bSpatrick     Rect variables_registers_bounds;
4447be691f3bSpatrick     source_variables_bounds.HorizontalSplitPercentage(
4448be691f3bSpatrick         0.70, source_bounds, variables_registers_bounds);
4449be691f3bSpatrick     if (variables_window_sp || registers_window_sp) {
4450be691f3bSpatrick       if (variables_window_sp && registers_window_sp) {
4451be691f3bSpatrick         Rect variables_bounds;
4452be691f3bSpatrick         Rect registers_bounds;
4453be691f3bSpatrick         variables_registers_bounds.VerticalSplitPercentage(
4454be691f3bSpatrick             0.50, variables_bounds, registers_bounds);
4455be691f3bSpatrick         variables_window_sp->SetBounds(variables_bounds);
4456be691f3bSpatrick         registers_window_sp->SetBounds(registers_bounds);
4457be691f3bSpatrick       } else if (variables_window_sp) {
4458be691f3bSpatrick         variables_window_sp->SetBounds(variables_registers_bounds);
4459be691f3bSpatrick       } else {
4460be691f3bSpatrick         registers_window_sp->SetBounds(variables_registers_bounds);
4461be691f3bSpatrick       }
4462be691f3bSpatrick     } else {
4463be691f3bSpatrick       source_bounds = source_variables_bounds;
4464be691f3bSpatrick     }
4465be691f3bSpatrick 
4466be691f3bSpatrick     source_window_sp->SetBounds(source_bounds);
4467be691f3bSpatrick 
4468be691f3bSpatrick     touchwin(stdscr);
4469be691f3bSpatrick     redrawwin(m_window_sp->get());
4470be691f3bSpatrick     m_update_screen = true;
4471be691f3bSpatrick   }
4472be691f3bSpatrick 
4473061da546Spatrick protected:
4474061da546Spatrick   WindowSP m_window_sp;
4475061da546Spatrick   WindowDelegates m_window_delegates;
4476*f6aab3d8Srobert   SCREEN *m_screen = nullptr;
4477061da546Spatrick   FILE *m_in;
4478061da546Spatrick   FILE *m_out;
4479be691f3bSpatrick   bool m_update_screen = false;
4480061da546Spatrick };
4481061da546Spatrick 
4482061da546Spatrick } // namespace curses
4483061da546Spatrick 
4484061da546Spatrick using namespace curses;
4485061da546Spatrick 
4486061da546Spatrick struct Row {
4487be691f3bSpatrick   ValueObjectUpdater value;
4488061da546Spatrick   Row *parent;
4489061da546Spatrick   // The process stop ID when the children were calculated.
4490be691f3bSpatrick   uint32_t children_stop_id = 0;
4491be691f3bSpatrick   int row_idx = 0;
4492be691f3bSpatrick   int x = 1;
4493be691f3bSpatrick   int y = 1;
4494061da546Spatrick   bool might_have_children;
4495be691f3bSpatrick   bool expanded = false;
4496be691f3bSpatrick   bool calculated_children = false;
4497061da546Spatrick   std::vector<Row> children;
4498061da546Spatrick 
RowRow4499061da546Spatrick   Row(const ValueObjectSP &v, Row *p)
4500be691f3bSpatrick       : value(v), parent(p),
4501be691f3bSpatrick         might_have_children(v ? v->MightHaveChildren() : false) {}
4502061da546Spatrick 
GetDepthRow4503061da546Spatrick   size_t GetDepth() const {
4504061da546Spatrick     if (parent)
4505061da546Spatrick       return 1 + parent->GetDepth();
4506061da546Spatrick     return 0;
4507061da546Spatrick   }
4508061da546Spatrick 
ExpandRow4509061da546Spatrick   void Expand() { expanded = true; }
4510061da546Spatrick 
GetChildrenRow4511061da546Spatrick   std::vector<Row> &GetChildren() {
4512061da546Spatrick     ProcessSP process_sp = value.GetProcessSP();
4513061da546Spatrick     auto stop_id = process_sp->GetStopID();
4514061da546Spatrick     if (process_sp && stop_id != children_stop_id) {
4515061da546Spatrick       children_stop_id = stop_id;
4516061da546Spatrick       calculated_children = false;
4517061da546Spatrick     }
4518061da546Spatrick     if (!calculated_children) {
4519061da546Spatrick       children.clear();
4520061da546Spatrick       calculated_children = true;
4521061da546Spatrick       ValueObjectSP valobj = value.GetSP();
4522061da546Spatrick       if (valobj) {
4523061da546Spatrick         const size_t num_children = valobj->GetNumChildren();
4524061da546Spatrick         for (size_t i = 0; i < num_children; ++i) {
4525061da546Spatrick           children.push_back(Row(valobj->GetChildAtIndex(i, true), this));
4526061da546Spatrick         }
4527061da546Spatrick       }
4528061da546Spatrick     }
4529061da546Spatrick     return children;
4530061da546Spatrick   }
4531061da546Spatrick 
UnexpandRow4532061da546Spatrick   void Unexpand() {
4533061da546Spatrick     expanded = false;
4534061da546Spatrick     calculated_children = false;
4535061da546Spatrick     children.clear();
4536061da546Spatrick   }
4537061da546Spatrick 
DrawTreeRow4538061da546Spatrick   void DrawTree(Window &window) {
4539061da546Spatrick     if (parent)
4540061da546Spatrick       parent->DrawTreeForChild(window, this, 0);
4541061da546Spatrick 
4542*f6aab3d8Srobert     if (might_have_children &&
4543*f6aab3d8Srobert         (!calculated_children || !GetChildren().empty())) {
4544061da546Spatrick       // It we can get UTF8 characters to work we should try to use the
4545061da546Spatrick       // "symbol" UTF8 string below
4546061da546Spatrick       //            const char *symbol = "";
4547061da546Spatrick       //            if (row.expanded)
4548061da546Spatrick       //                symbol = "\xe2\x96\xbd ";
4549061da546Spatrick       //            else
4550061da546Spatrick       //                symbol = "\xe2\x96\xb7 ";
4551061da546Spatrick       //            window.PutCString (symbol);
4552061da546Spatrick 
4553061da546Spatrick       // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v'
4554061da546Spatrick       // or '>' character...
4555061da546Spatrick       //            if (expanded)
4556061da546Spatrick       //                window.PutChar (ACS_DARROW);
4557061da546Spatrick       //            else
4558061da546Spatrick       //                window.PutChar (ACS_RARROW);
4559061da546Spatrick       // Since we can't find any good looking right arrow/down arrow symbols,
4560061da546Spatrick       // just use a diamond...
4561061da546Spatrick       window.PutChar(ACS_DIAMOND);
4562061da546Spatrick       window.PutChar(ACS_HLINE);
4563061da546Spatrick     }
4564061da546Spatrick   }
4565061da546Spatrick 
DrawTreeForChildRow4566061da546Spatrick   void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) {
4567061da546Spatrick     if (parent)
4568061da546Spatrick       parent->DrawTreeForChild(window, this, reverse_depth + 1);
4569061da546Spatrick 
4570061da546Spatrick     if (&GetChildren().back() == child) {
4571061da546Spatrick       // Last child
4572061da546Spatrick       if (reverse_depth == 0) {
4573061da546Spatrick         window.PutChar(ACS_LLCORNER);
4574061da546Spatrick         window.PutChar(ACS_HLINE);
4575061da546Spatrick       } else {
4576061da546Spatrick         window.PutChar(' ');
4577061da546Spatrick         window.PutChar(' ');
4578061da546Spatrick       }
4579061da546Spatrick     } else {
4580061da546Spatrick       if (reverse_depth == 0) {
4581061da546Spatrick         window.PutChar(ACS_LTEE);
4582061da546Spatrick         window.PutChar(ACS_HLINE);
4583061da546Spatrick       } else {
4584061da546Spatrick         window.PutChar(ACS_VLINE);
4585061da546Spatrick         window.PutChar(' ');
4586061da546Spatrick       }
4587061da546Spatrick     }
4588061da546Spatrick   }
4589061da546Spatrick };
4590061da546Spatrick 
4591061da546Spatrick struct DisplayOptions {
4592061da546Spatrick   bool show_types;
4593061da546Spatrick };
4594061da546Spatrick 
4595061da546Spatrick class TreeItem;
4596061da546Spatrick 
4597061da546Spatrick class TreeDelegate {
4598061da546Spatrick public:
4599061da546Spatrick   TreeDelegate() = default;
4600061da546Spatrick   virtual ~TreeDelegate() = default;
4601061da546Spatrick 
4602061da546Spatrick   virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0;
4603061da546Spatrick   virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0;
TreeDelegateUpdateSelection(TreeItem & root,int & selection_index,TreeItem * & selected_item)4604be691f3bSpatrick   virtual void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
4605*f6aab3d8Srobert                                            TreeItem *&selected_item) {}
4606*f6aab3d8Srobert   // This is invoked when a tree item is selected. If true is returned, the
4607*f6aab3d8Srobert   // views are updated.
4608*f6aab3d8Srobert   virtual bool TreeDelegateItemSelected(TreeItem &item) = 0;
TreeDelegateExpandRootByDefault()4609be691f3bSpatrick   virtual bool TreeDelegateExpandRootByDefault() { return false; }
4610*f6aab3d8Srobert   // This is mostly useful for root tree delegates. If false is returned,
4611*f6aab3d8Srobert   // drawing will be skipped completely. This is needed, for instance, in
4612*f6aab3d8Srobert   // skipping drawing of the threads tree if there is no running process.
TreeDelegateShouldDraw()4613*f6aab3d8Srobert   virtual bool TreeDelegateShouldDraw() { return true; }
4614061da546Spatrick };
4615061da546Spatrick 
4616061da546Spatrick typedef std::shared_ptr<TreeDelegate> TreeDelegateSP;
4617061da546Spatrick 
4618061da546Spatrick class TreeItem {
4619061da546Spatrick public:
TreeItem(TreeItem * parent,TreeDelegate & delegate,bool might_have_children)4620061da546Spatrick   TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children)
4621*f6aab3d8Srobert       : m_parent(parent), m_delegate(delegate), m_children(),
4622*f6aab3d8Srobert         m_might_have_children(might_have_children) {
4623be691f3bSpatrick     if (m_parent == nullptr)
4624be691f3bSpatrick       m_is_expanded = m_delegate.TreeDelegateExpandRootByDefault();
4625be691f3bSpatrick   }
4626061da546Spatrick 
operator =(const TreeItem & rhs)4627061da546Spatrick   TreeItem &operator=(const TreeItem &rhs) {
4628061da546Spatrick     if (this != &rhs) {
4629061da546Spatrick       m_parent = rhs.m_parent;
4630061da546Spatrick       m_delegate = rhs.m_delegate;
4631061da546Spatrick       m_user_data = rhs.m_user_data;
4632061da546Spatrick       m_identifier = rhs.m_identifier;
4633061da546Spatrick       m_row_idx = rhs.m_row_idx;
4634061da546Spatrick       m_children = rhs.m_children;
4635061da546Spatrick       m_might_have_children = rhs.m_might_have_children;
4636061da546Spatrick       m_is_expanded = rhs.m_is_expanded;
4637061da546Spatrick     }
4638061da546Spatrick     return *this;
4639061da546Spatrick   }
4640061da546Spatrick 
4641061da546Spatrick   TreeItem(const TreeItem &) = default;
4642061da546Spatrick 
GetDepth() const4643061da546Spatrick   size_t GetDepth() const {
4644061da546Spatrick     if (m_parent)
4645061da546Spatrick       return 1 + m_parent->GetDepth();
4646061da546Spatrick     return 0;
4647061da546Spatrick   }
4648061da546Spatrick 
GetRowIndex() const4649061da546Spatrick   int GetRowIndex() const { return m_row_idx; }
4650061da546Spatrick 
ClearChildren()4651061da546Spatrick   void ClearChildren() { m_children.clear(); }
4652061da546Spatrick 
Resize(size_t n,const TreeItem & t)4653061da546Spatrick   void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); }
4654061da546Spatrick 
operator [](size_t i)4655061da546Spatrick   TreeItem &operator[](size_t i) { return m_children[i]; }
4656061da546Spatrick 
SetRowIndex(int row_idx)4657061da546Spatrick   void SetRowIndex(int row_idx) { m_row_idx = row_idx; }
4658061da546Spatrick 
GetNumChildren()4659061da546Spatrick   size_t GetNumChildren() {
4660061da546Spatrick     m_delegate.TreeDelegateGenerateChildren(*this);
4661061da546Spatrick     return m_children.size();
4662061da546Spatrick   }
4663061da546Spatrick 
ItemWasSelected()4664061da546Spatrick   void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); }
4665061da546Spatrick 
CalculateRowIndexes(int & row_idx)4666061da546Spatrick   void CalculateRowIndexes(int &row_idx) {
4667061da546Spatrick     SetRowIndex(row_idx);
4668061da546Spatrick     ++row_idx;
4669061da546Spatrick 
4670061da546Spatrick     const bool expanded = IsExpanded();
4671061da546Spatrick 
4672061da546Spatrick     // The root item must calculate its children, or we must calculate the
4673061da546Spatrick     // number of children if the item is expanded
4674061da546Spatrick     if (m_parent == nullptr || expanded)
4675061da546Spatrick       GetNumChildren();
4676061da546Spatrick 
4677061da546Spatrick     for (auto &item : m_children) {
4678061da546Spatrick       if (expanded)
4679061da546Spatrick         item.CalculateRowIndexes(row_idx);
4680061da546Spatrick       else
4681061da546Spatrick         item.SetRowIndex(-1);
4682061da546Spatrick     }
4683061da546Spatrick   }
4684061da546Spatrick 
GetParent()4685061da546Spatrick   TreeItem *GetParent() { return m_parent; }
4686061da546Spatrick 
IsExpanded() const4687061da546Spatrick   bool IsExpanded() const { return m_is_expanded; }
4688061da546Spatrick 
Expand()4689061da546Spatrick   void Expand() { m_is_expanded = true; }
4690061da546Spatrick 
Unexpand()4691061da546Spatrick   void Unexpand() { m_is_expanded = false; }
4692061da546Spatrick 
Draw(Window & window,const int first_visible_row,const uint32_t selected_row_idx,int & row_idx,int & num_rows_left)4693061da546Spatrick   bool Draw(Window &window, const int first_visible_row,
4694061da546Spatrick             const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) {
4695061da546Spatrick     if (num_rows_left <= 0)
4696061da546Spatrick       return false;
4697061da546Spatrick 
4698061da546Spatrick     if (m_row_idx >= first_visible_row) {
4699061da546Spatrick       window.MoveCursor(2, row_idx + 1);
4700061da546Spatrick 
4701061da546Spatrick       if (m_parent)
4702061da546Spatrick         m_parent->DrawTreeForChild(window, this, 0);
4703061da546Spatrick 
4704061da546Spatrick       if (m_might_have_children) {
4705061da546Spatrick         // It we can get UTF8 characters to work we should try to use the
4706061da546Spatrick         // "symbol" UTF8 string below
4707061da546Spatrick         //            const char *symbol = "";
4708061da546Spatrick         //            if (row.expanded)
4709061da546Spatrick         //                symbol = "\xe2\x96\xbd ";
4710061da546Spatrick         //            else
4711061da546Spatrick         //                symbol = "\xe2\x96\xb7 ";
4712061da546Spatrick         //            window.PutCString (symbol);
4713061da546Spatrick 
4714061da546Spatrick         // The ACS_DARROW and ACS_RARROW don't look very nice they are just a
4715061da546Spatrick         // 'v' or '>' character...
4716061da546Spatrick         //            if (expanded)
4717061da546Spatrick         //                window.PutChar (ACS_DARROW);
4718061da546Spatrick         //            else
4719061da546Spatrick         //                window.PutChar (ACS_RARROW);
4720061da546Spatrick         // Since we can't find any good looking right arrow/down arrow symbols,
4721061da546Spatrick         // just use a diamond...
4722061da546Spatrick         window.PutChar(ACS_DIAMOND);
4723061da546Spatrick         window.PutChar(ACS_HLINE);
4724061da546Spatrick       }
4725061da546Spatrick       bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) &&
4726061da546Spatrick                        window.IsActive();
4727061da546Spatrick 
4728061da546Spatrick       if (highlight)
4729061da546Spatrick         window.AttributeOn(A_REVERSE);
4730061da546Spatrick 
4731061da546Spatrick       m_delegate.TreeDelegateDrawTreeItem(*this, window);
4732061da546Spatrick 
4733061da546Spatrick       if (highlight)
4734061da546Spatrick         window.AttributeOff(A_REVERSE);
4735061da546Spatrick       ++row_idx;
4736061da546Spatrick       --num_rows_left;
4737061da546Spatrick     }
4738061da546Spatrick 
4739061da546Spatrick     if (num_rows_left <= 0)
4740061da546Spatrick       return false; // We are done drawing...
4741061da546Spatrick 
4742061da546Spatrick     if (IsExpanded()) {
4743061da546Spatrick       for (auto &item : m_children) {
4744061da546Spatrick         // If we displayed all the rows and item.Draw() returns false we are
4745061da546Spatrick         // done drawing and can exit this for loop
4746061da546Spatrick         if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx,
4747061da546Spatrick                        num_rows_left))
4748061da546Spatrick           break;
4749061da546Spatrick       }
4750061da546Spatrick     }
4751061da546Spatrick     return num_rows_left >= 0; // Return true if not done drawing yet
4752061da546Spatrick   }
4753061da546Spatrick 
DrawTreeForChild(Window & window,TreeItem * child,uint32_t reverse_depth)4754061da546Spatrick   void DrawTreeForChild(Window &window, TreeItem *child,
4755061da546Spatrick                         uint32_t reverse_depth) {
4756061da546Spatrick     if (m_parent)
4757061da546Spatrick       m_parent->DrawTreeForChild(window, this, reverse_depth + 1);
4758061da546Spatrick 
4759061da546Spatrick     if (&m_children.back() == child) {
4760061da546Spatrick       // Last child
4761061da546Spatrick       if (reverse_depth == 0) {
4762061da546Spatrick         window.PutChar(ACS_LLCORNER);
4763061da546Spatrick         window.PutChar(ACS_HLINE);
4764061da546Spatrick       } else {
4765061da546Spatrick         window.PutChar(' ');
4766061da546Spatrick         window.PutChar(' ');
4767061da546Spatrick       }
4768061da546Spatrick     } else {
4769061da546Spatrick       if (reverse_depth == 0) {
4770061da546Spatrick         window.PutChar(ACS_LTEE);
4771061da546Spatrick         window.PutChar(ACS_HLINE);
4772061da546Spatrick       } else {
4773061da546Spatrick         window.PutChar(ACS_VLINE);
4774061da546Spatrick         window.PutChar(' ');
4775061da546Spatrick       }
4776061da546Spatrick     }
4777061da546Spatrick   }
4778061da546Spatrick 
GetItemForRowIndex(uint32_t row_idx)4779061da546Spatrick   TreeItem *GetItemForRowIndex(uint32_t row_idx) {
4780061da546Spatrick     if (static_cast<uint32_t>(m_row_idx) == row_idx)
4781061da546Spatrick       return this;
4782061da546Spatrick     if (m_children.empty())
4783061da546Spatrick       return nullptr;
4784061da546Spatrick     if (IsExpanded()) {
4785061da546Spatrick       for (auto &item : m_children) {
4786061da546Spatrick         TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx);
4787061da546Spatrick         if (selected_item_ptr)
4788061da546Spatrick           return selected_item_ptr;
4789061da546Spatrick       }
4790061da546Spatrick     }
4791061da546Spatrick     return nullptr;
4792061da546Spatrick   }
4793061da546Spatrick 
GetUserData() const4794061da546Spatrick   void *GetUserData() const { return m_user_data; }
4795061da546Spatrick 
SetUserData(void * user_data)4796061da546Spatrick   void SetUserData(void *user_data) { m_user_data = user_data; }
4797061da546Spatrick 
GetIdentifier() const4798061da546Spatrick   uint64_t GetIdentifier() const { return m_identifier; }
4799061da546Spatrick 
SetIdentifier(uint64_t identifier)4800061da546Spatrick   void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
4801061da546Spatrick 
GetText() const4802*f6aab3d8Srobert   const std::string &GetText() const { return m_text; }
4803*f6aab3d8Srobert 
SetText(const char * text)4804*f6aab3d8Srobert   void SetText(const char *text) {
4805*f6aab3d8Srobert     if (text == nullptr) {
4806*f6aab3d8Srobert       m_text.clear();
4807*f6aab3d8Srobert       return;
4808*f6aab3d8Srobert     }
4809*f6aab3d8Srobert     m_text = text;
4810*f6aab3d8Srobert   }
4811*f6aab3d8Srobert 
SetMightHaveChildren(bool b)4812061da546Spatrick   void SetMightHaveChildren(bool b) { m_might_have_children = b; }
4813061da546Spatrick 
4814061da546Spatrick protected:
4815061da546Spatrick   TreeItem *m_parent;
4816061da546Spatrick   TreeDelegate &m_delegate;
4817*f6aab3d8Srobert   void *m_user_data = nullptr;
4818*f6aab3d8Srobert   uint64_t m_identifier = 0;
4819*f6aab3d8Srobert   std::string m_text;
4820*f6aab3d8Srobert   int m_row_idx = -1; // Zero based visible row index, -1 if not visible or for
4821*f6aab3d8Srobert                       // the root item
4822061da546Spatrick   std::vector<TreeItem> m_children;
4823061da546Spatrick   bool m_might_have_children;
4824*f6aab3d8Srobert   bool m_is_expanded = false;
4825061da546Spatrick };
4826061da546Spatrick 
4827061da546Spatrick class TreeWindowDelegate : public WindowDelegate {
4828061da546Spatrick public:
TreeWindowDelegate(Debugger & debugger,const TreeDelegateSP & delegate_sp)4829061da546Spatrick   TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp)
4830061da546Spatrick       : m_debugger(debugger), m_delegate_sp(delegate_sp),
4831*f6aab3d8Srobert         m_root(nullptr, *delegate_sp, true) {}
4832061da546Spatrick 
NumVisibleRows() const4833061da546Spatrick   int NumVisibleRows() const { return m_max_y - m_min_y; }
4834061da546Spatrick 
WindowDelegateDraw(Window & window,bool force)4835061da546Spatrick   bool WindowDelegateDraw(Window &window, bool force) override {
4836061da546Spatrick     m_min_x = 2;
4837061da546Spatrick     m_min_y = 1;
4838061da546Spatrick     m_max_x = window.GetWidth() - 1;
4839061da546Spatrick     m_max_y = window.GetHeight() - 1;
4840061da546Spatrick 
4841061da546Spatrick     window.Erase();
4842061da546Spatrick     window.DrawTitleBox(window.GetName());
4843061da546Spatrick 
4844*f6aab3d8Srobert     if (!m_delegate_sp->TreeDelegateShouldDraw()) {
4845*f6aab3d8Srobert       m_selected_item = nullptr;
4846*f6aab3d8Srobert       return true;
4847*f6aab3d8Srobert     }
4848*f6aab3d8Srobert 
4849061da546Spatrick     const int num_visible_rows = NumVisibleRows();
4850061da546Spatrick     m_num_rows = 0;
4851061da546Spatrick     m_root.CalculateRowIndexes(m_num_rows);
4852be691f3bSpatrick     m_delegate_sp->TreeDelegateUpdateSelection(m_root, m_selected_row_idx,
4853be691f3bSpatrick                                                m_selected_item);
4854061da546Spatrick 
4855061da546Spatrick     // If we unexpanded while having something selected our total number of
4856061da546Spatrick     // rows is less than the num visible rows, then make sure we show all the
4857061da546Spatrick     // rows by setting the first visible row accordingly.
4858061da546Spatrick     if (m_first_visible_row > 0 && m_num_rows < num_visible_rows)
4859061da546Spatrick       m_first_visible_row = 0;
4860061da546Spatrick 
4861061da546Spatrick     // Make sure the selected row is always visible
4862061da546Spatrick     if (m_selected_row_idx < m_first_visible_row)
4863061da546Spatrick       m_first_visible_row = m_selected_row_idx;
4864061da546Spatrick     else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
4865061da546Spatrick       m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
4866061da546Spatrick 
4867061da546Spatrick     int row_idx = 0;
4868061da546Spatrick     int num_rows_left = num_visible_rows;
4869061da546Spatrick     m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx,
4870061da546Spatrick                 num_rows_left);
4871061da546Spatrick     // Get the selected row
4872061da546Spatrick     m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4873061da546Spatrick 
4874061da546Spatrick     return true; // Drawing handled
4875061da546Spatrick   }
4876061da546Spatrick 
WindowDelegateGetHelpText()4877061da546Spatrick   const char *WindowDelegateGetHelpText() override {
4878061da546Spatrick     return "Thread window keyboard shortcuts:";
4879061da546Spatrick   }
4880061da546Spatrick 
WindowDelegateGetKeyHelp()4881061da546Spatrick   KeyHelp *WindowDelegateGetKeyHelp() override {
4882061da546Spatrick     static curses::KeyHelp g_source_view_key_help[] = {
4883061da546Spatrick         {KEY_UP, "Select previous item"},
4884061da546Spatrick         {KEY_DOWN, "Select next item"},
4885061da546Spatrick         {KEY_RIGHT, "Expand the selected item"},
4886061da546Spatrick         {KEY_LEFT,
4887061da546Spatrick          "Unexpand the selected item or select parent if not expanded"},
4888061da546Spatrick         {KEY_PPAGE, "Page up"},
4889061da546Spatrick         {KEY_NPAGE, "Page down"},
4890061da546Spatrick         {'h', "Show help dialog"},
4891061da546Spatrick         {' ', "Toggle item expansion"},
4892061da546Spatrick         {',', "Page up"},
4893061da546Spatrick         {'.', "Page down"},
4894061da546Spatrick         {'\0', nullptr}};
4895061da546Spatrick     return g_source_view_key_help;
4896061da546Spatrick   }
4897061da546Spatrick 
WindowDelegateHandleChar(Window & window,int c)4898061da546Spatrick   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
4899061da546Spatrick     switch (c) {
4900061da546Spatrick     case ',':
4901061da546Spatrick     case KEY_PPAGE:
4902061da546Spatrick       // Page up key
4903061da546Spatrick       if (m_first_visible_row > 0) {
4904061da546Spatrick         if (m_first_visible_row > m_max_y)
4905061da546Spatrick           m_first_visible_row -= m_max_y;
4906061da546Spatrick         else
4907061da546Spatrick           m_first_visible_row = 0;
4908061da546Spatrick         m_selected_row_idx = m_first_visible_row;
4909061da546Spatrick         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4910061da546Spatrick         if (m_selected_item)
4911061da546Spatrick           m_selected_item->ItemWasSelected();
4912061da546Spatrick       }
4913061da546Spatrick       return eKeyHandled;
4914061da546Spatrick 
4915061da546Spatrick     case '.':
4916061da546Spatrick     case KEY_NPAGE:
4917061da546Spatrick       // Page down key
4918061da546Spatrick       if (m_num_rows > m_max_y) {
4919061da546Spatrick         if (m_first_visible_row + m_max_y < m_num_rows) {
4920061da546Spatrick           m_first_visible_row += m_max_y;
4921061da546Spatrick           m_selected_row_idx = m_first_visible_row;
4922061da546Spatrick           m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4923061da546Spatrick           if (m_selected_item)
4924061da546Spatrick             m_selected_item->ItemWasSelected();
4925061da546Spatrick         }
4926061da546Spatrick       }
4927061da546Spatrick       return eKeyHandled;
4928061da546Spatrick 
4929061da546Spatrick     case KEY_UP:
4930061da546Spatrick       if (m_selected_row_idx > 0) {
4931061da546Spatrick         --m_selected_row_idx;
4932061da546Spatrick         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4933061da546Spatrick         if (m_selected_item)
4934061da546Spatrick           m_selected_item->ItemWasSelected();
4935061da546Spatrick       }
4936061da546Spatrick       return eKeyHandled;
4937061da546Spatrick 
4938061da546Spatrick     case KEY_DOWN:
4939061da546Spatrick       if (m_selected_row_idx + 1 < m_num_rows) {
4940061da546Spatrick         ++m_selected_row_idx;
4941061da546Spatrick         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4942061da546Spatrick         if (m_selected_item)
4943061da546Spatrick           m_selected_item->ItemWasSelected();
4944061da546Spatrick       }
4945061da546Spatrick       return eKeyHandled;
4946061da546Spatrick 
4947061da546Spatrick     case KEY_RIGHT:
4948061da546Spatrick       if (m_selected_item) {
4949061da546Spatrick         if (!m_selected_item->IsExpanded())
4950061da546Spatrick           m_selected_item->Expand();
4951061da546Spatrick       }
4952061da546Spatrick       return eKeyHandled;
4953061da546Spatrick 
4954061da546Spatrick     case KEY_LEFT:
4955061da546Spatrick       if (m_selected_item) {
4956061da546Spatrick         if (m_selected_item->IsExpanded())
4957061da546Spatrick           m_selected_item->Unexpand();
4958061da546Spatrick         else if (m_selected_item->GetParent()) {
4959061da546Spatrick           m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex();
4960061da546Spatrick           m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4961061da546Spatrick           if (m_selected_item)
4962061da546Spatrick             m_selected_item->ItemWasSelected();
4963061da546Spatrick         }
4964061da546Spatrick       }
4965061da546Spatrick       return eKeyHandled;
4966061da546Spatrick 
4967061da546Spatrick     case ' ':
4968061da546Spatrick       // Toggle expansion state when SPACE is pressed
4969061da546Spatrick       if (m_selected_item) {
4970061da546Spatrick         if (m_selected_item->IsExpanded())
4971061da546Spatrick           m_selected_item->Unexpand();
4972061da546Spatrick         else
4973061da546Spatrick           m_selected_item->Expand();
4974061da546Spatrick       }
4975061da546Spatrick       return eKeyHandled;
4976061da546Spatrick 
4977061da546Spatrick     case 'h':
4978061da546Spatrick       window.CreateHelpSubwindow();
4979061da546Spatrick       return eKeyHandled;
4980061da546Spatrick 
4981061da546Spatrick     default:
4982061da546Spatrick       break;
4983061da546Spatrick     }
4984061da546Spatrick     return eKeyNotHandled;
4985061da546Spatrick   }
4986061da546Spatrick 
4987061da546Spatrick protected:
4988061da546Spatrick   Debugger &m_debugger;
4989061da546Spatrick   TreeDelegateSP m_delegate_sp;
4990061da546Spatrick   TreeItem m_root;
4991*f6aab3d8Srobert   TreeItem *m_selected_item = nullptr;
4992*f6aab3d8Srobert   int m_num_rows = 0;
4993*f6aab3d8Srobert   int m_selected_row_idx = 0;
4994*f6aab3d8Srobert   int m_first_visible_row = 0;
4995*f6aab3d8Srobert   int m_min_x = 0;
4996*f6aab3d8Srobert   int m_min_y = 0;
4997*f6aab3d8Srobert   int m_max_x = 0;
4998*f6aab3d8Srobert   int m_max_y = 0;
4999*f6aab3d8Srobert };
5000*f6aab3d8Srobert 
5001*f6aab3d8Srobert // A tree delegate that just draws the text member of the tree item, it doesn't
5002*f6aab3d8Srobert // have any children or actions.
5003*f6aab3d8Srobert class TextTreeDelegate : public TreeDelegate {
5004*f6aab3d8Srobert public:
TextTreeDelegate()5005*f6aab3d8Srobert   TextTreeDelegate() : TreeDelegate() {}
5006*f6aab3d8Srobert 
5007*f6aab3d8Srobert   ~TextTreeDelegate() override = default;
5008*f6aab3d8Srobert 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5009*f6aab3d8Srobert   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5010*f6aab3d8Srobert     window.PutCStringTruncated(1, item.GetText().c_str());
5011*f6aab3d8Srobert   }
5012*f6aab3d8Srobert 
TreeDelegateGenerateChildren(TreeItem & item)5013*f6aab3d8Srobert   void TreeDelegateGenerateChildren(TreeItem &item) override {}
5014*f6aab3d8Srobert 
TreeDelegateItemSelected(TreeItem & item)5015*f6aab3d8Srobert   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5016061da546Spatrick };
5017061da546Spatrick 
5018061da546Spatrick class FrameTreeDelegate : public TreeDelegate {
5019061da546Spatrick public:
FrameTreeDelegate()5020061da546Spatrick   FrameTreeDelegate() : TreeDelegate() {
5021061da546Spatrick     FormatEntity::Parse(
5022*f6aab3d8Srobert         "#${frame.index}: {${function.name}${function.pc-offset}}}", m_format);
5023061da546Spatrick   }
5024061da546Spatrick 
5025061da546Spatrick   ~FrameTreeDelegate() override = default;
5026061da546Spatrick 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5027061da546Spatrick   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5028061da546Spatrick     Thread *thread = (Thread *)item.GetUserData();
5029061da546Spatrick     if (thread) {
5030061da546Spatrick       const uint64_t frame_idx = item.GetIdentifier();
5031061da546Spatrick       StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx);
5032061da546Spatrick       if (frame_sp) {
5033061da546Spatrick         StreamString strm;
5034061da546Spatrick         const SymbolContext &sc =
5035061da546Spatrick             frame_sp->GetSymbolContext(eSymbolContextEverything);
5036061da546Spatrick         ExecutionContext exe_ctx(frame_sp);
5037061da546Spatrick         if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr,
5038061da546Spatrick                                  nullptr, false, false)) {
5039061da546Spatrick           int right_pad = 1;
5040be691f3bSpatrick           window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
5041061da546Spatrick         }
5042061da546Spatrick       }
5043061da546Spatrick     }
5044061da546Spatrick   }
5045061da546Spatrick 
TreeDelegateGenerateChildren(TreeItem & item)5046061da546Spatrick   void TreeDelegateGenerateChildren(TreeItem &item) override {
5047061da546Spatrick     // No children for frames yet...
5048061da546Spatrick   }
5049061da546Spatrick 
TreeDelegateItemSelected(TreeItem & item)5050061da546Spatrick   bool TreeDelegateItemSelected(TreeItem &item) override {
5051061da546Spatrick     Thread *thread = (Thread *)item.GetUserData();
5052061da546Spatrick     if (thread) {
5053061da546Spatrick       thread->GetProcess()->GetThreadList().SetSelectedThreadByID(
5054061da546Spatrick           thread->GetID());
5055061da546Spatrick       const uint64_t frame_idx = item.GetIdentifier();
5056061da546Spatrick       thread->SetSelectedFrameByIndex(frame_idx);
5057061da546Spatrick       return true;
5058061da546Spatrick     }
5059061da546Spatrick     return false;
5060061da546Spatrick   }
5061061da546Spatrick 
5062061da546Spatrick protected:
5063061da546Spatrick   FormatEntity::Entry m_format;
5064061da546Spatrick };
5065061da546Spatrick 
5066061da546Spatrick class ThreadTreeDelegate : public TreeDelegate {
5067061da546Spatrick public:
ThreadTreeDelegate(Debugger & debugger)5068061da546Spatrick   ThreadTreeDelegate(Debugger &debugger)
5069*f6aab3d8Srobert       : TreeDelegate(), m_debugger(debugger) {
5070061da546Spatrick     FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop "
5071061da546Spatrick                         "reason = ${thread.stop-reason}}",
5072061da546Spatrick                         m_format);
5073061da546Spatrick   }
5074061da546Spatrick 
5075061da546Spatrick   ~ThreadTreeDelegate() override = default;
5076061da546Spatrick 
GetProcess()5077061da546Spatrick   ProcessSP GetProcess() {
5078061da546Spatrick     return m_debugger.GetCommandInterpreter()
5079061da546Spatrick         .GetExecutionContext()
5080061da546Spatrick         .GetProcessSP();
5081061da546Spatrick   }
5082061da546Spatrick 
GetThread(const TreeItem & item)5083061da546Spatrick   ThreadSP GetThread(const TreeItem &item) {
5084061da546Spatrick     ProcessSP process_sp = GetProcess();
5085061da546Spatrick     if (process_sp)
5086061da546Spatrick       return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier());
5087061da546Spatrick     return ThreadSP();
5088061da546Spatrick   }
5089061da546Spatrick 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5090061da546Spatrick   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5091061da546Spatrick     ThreadSP thread_sp = GetThread(item);
5092061da546Spatrick     if (thread_sp) {
5093061da546Spatrick       StreamString strm;
5094061da546Spatrick       ExecutionContext exe_ctx(thread_sp);
5095061da546Spatrick       if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
5096061da546Spatrick                                nullptr, false, false)) {
5097061da546Spatrick         int right_pad = 1;
5098be691f3bSpatrick         window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
5099061da546Spatrick       }
5100061da546Spatrick     }
5101061da546Spatrick   }
5102061da546Spatrick 
TreeDelegateGenerateChildren(TreeItem & item)5103061da546Spatrick   void TreeDelegateGenerateChildren(TreeItem &item) override {
5104061da546Spatrick     ProcessSP process_sp = GetProcess();
5105061da546Spatrick     if (process_sp && process_sp->IsAlive()) {
5106061da546Spatrick       StateType state = process_sp->GetState();
5107061da546Spatrick       if (StateIsStoppedState(state, true)) {
5108061da546Spatrick         ThreadSP thread_sp = GetThread(item);
5109061da546Spatrick         if (thread_sp) {
5110061da546Spatrick           if (m_stop_id == process_sp->GetStopID() &&
5111061da546Spatrick               thread_sp->GetID() == m_tid)
5112061da546Spatrick             return; // Children are already up to date
5113061da546Spatrick           if (!m_frame_delegate_sp) {
5114061da546Spatrick             // Always expand the thread item the first time we show it
5115061da546Spatrick             m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>();
5116061da546Spatrick           }
5117061da546Spatrick 
5118061da546Spatrick           m_stop_id = process_sp->GetStopID();
5119061da546Spatrick           m_tid = thread_sp->GetID();
5120061da546Spatrick 
5121061da546Spatrick           TreeItem t(&item, *m_frame_delegate_sp, false);
5122061da546Spatrick           size_t num_frames = thread_sp->GetStackFrameCount();
5123061da546Spatrick           item.Resize(num_frames, t);
5124061da546Spatrick           for (size_t i = 0; i < num_frames; ++i) {
5125061da546Spatrick             item[i].SetUserData(thread_sp.get());
5126061da546Spatrick             item[i].SetIdentifier(i);
5127061da546Spatrick           }
5128061da546Spatrick         }
5129061da546Spatrick         return;
5130061da546Spatrick       }
5131061da546Spatrick     }
5132061da546Spatrick     item.ClearChildren();
5133061da546Spatrick   }
5134061da546Spatrick 
TreeDelegateItemSelected(TreeItem & item)5135061da546Spatrick   bool TreeDelegateItemSelected(TreeItem &item) override {
5136061da546Spatrick     ProcessSP process_sp = GetProcess();
5137061da546Spatrick     if (process_sp && process_sp->IsAlive()) {
5138061da546Spatrick       StateType state = process_sp->GetState();
5139061da546Spatrick       if (StateIsStoppedState(state, true)) {
5140061da546Spatrick         ThreadSP thread_sp = GetThread(item);
5141061da546Spatrick         if (thread_sp) {
5142061da546Spatrick           ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList();
5143061da546Spatrick           std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex());
5144061da546Spatrick           ThreadSP selected_thread_sp = thread_list.GetSelectedThread();
5145061da546Spatrick           if (selected_thread_sp->GetID() != thread_sp->GetID()) {
5146061da546Spatrick             thread_list.SetSelectedThreadByID(thread_sp->GetID());
5147061da546Spatrick             return true;
5148061da546Spatrick           }
5149061da546Spatrick         }
5150061da546Spatrick       }
5151061da546Spatrick     }
5152061da546Spatrick     return false;
5153061da546Spatrick   }
5154061da546Spatrick 
5155061da546Spatrick protected:
5156061da546Spatrick   Debugger &m_debugger;
5157061da546Spatrick   std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp;
5158*f6aab3d8Srobert   lldb::user_id_t m_tid = LLDB_INVALID_THREAD_ID;
5159*f6aab3d8Srobert   uint32_t m_stop_id = UINT32_MAX;
5160061da546Spatrick   FormatEntity::Entry m_format;
5161061da546Spatrick };
5162061da546Spatrick 
5163061da546Spatrick class ThreadsTreeDelegate : public TreeDelegate {
5164061da546Spatrick public:
ThreadsTreeDelegate(Debugger & debugger)5165061da546Spatrick   ThreadsTreeDelegate(Debugger &debugger)
5166*f6aab3d8Srobert       : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger) {
5167061da546Spatrick     FormatEntity::Parse("process ${process.id}{, name = ${process.name}}",
5168061da546Spatrick                         m_format);
5169061da546Spatrick   }
5170061da546Spatrick 
5171061da546Spatrick   ~ThreadsTreeDelegate() override = default;
5172061da546Spatrick 
GetProcess()5173061da546Spatrick   ProcessSP GetProcess() {
5174061da546Spatrick     return m_debugger.GetCommandInterpreter()
5175061da546Spatrick         .GetExecutionContext()
5176061da546Spatrick         .GetProcessSP();
5177061da546Spatrick   }
5178061da546Spatrick 
TreeDelegateShouldDraw()5179*f6aab3d8Srobert   bool TreeDelegateShouldDraw() override {
5180*f6aab3d8Srobert     ProcessSP process = GetProcess();
5181*f6aab3d8Srobert     if (!process)
5182*f6aab3d8Srobert       return false;
5183*f6aab3d8Srobert 
5184*f6aab3d8Srobert     if (StateIsRunningState(process->GetState()))
5185*f6aab3d8Srobert       return false;
5186*f6aab3d8Srobert 
5187*f6aab3d8Srobert     return true;
5188*f6aab3d8Srobert   }
5189*f6aab3d8Srobert 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5190061da546Spatrick   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5191061da546Spatrick     ProcessSP process_sp = GetProcess();
5192061da546Spatrick     if (process_sp && process_sp->IsAlive()) {
5193061da546Spatrick       StreamString strm;
5194061da546Spatrick       ExecutionContext exe_ctx(process_sp);
5195061da546Spatrick       if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
5196061da546Spatrick                                nullptr, false, false)) {
5197061da546Spatrick         int right_pad = 1;
5198be691f3bSpatrick         window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
5199061da546Spatrick       }
5200061da546Spatrick     }
5201061da546Spatrick   }
5202061da546Spatrick 
TreeDelegateGenerateChildren(TreeItem & item)5203061da546Spatrick   void TreeDelegateGenerateChildren(TreeItem &item) override {
5204061da546Spatrick     ProcessSP process_sp = GetProcess();
5205be691f3bSpatrick     m_update_selection = false;
5206061da546Spatrick     if (process_sp && process_sp->IsAlive()) {
5207061da546Spatrick       StateType state = process_sp->GetState();
5208061da546Spatrick       if (StateIsStoppedState(state, true)) {
5209061da546Spatrick         const uint32_t stop_id = process_sp->GetStopID();
5210061da546Spatrick         if (m_stop_id == stop_id)
5211061da546Spatrick           return; // Children are already up to date
5212061da546Spatrick 
5213061da546Spatrick         m_stop_id = stop_id;
5214be691f3bSpatrick         m_update_selection = true;
5215061da546Spatrick 
5216061da546Spatrick         if (!m_thread_delegate_sp) {
5217061da546Spatrick           // Always expand the thread item the first time we show it
5218061da546Spatrick           // item.Expand();
5219061da546Spatrick           m_thread_delegate_sp =
5220061da546Spatrick               std::make_shared<ThreadTreeDelegate>(m_debugger);
5221061da546Spatrick         }
5222061da546Spatrick 
5223061da546Spatrick         TreeItem t(&item, *m_thread_delegate_sp, false);
5224061da546Spatrick         ThreadList &threads = process_sp->GetThreadList();
5225061da546Spatrick         std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
5226be691f3bSpatrick         ThreadSP selected_thread = threads.GetSelectedThread();
5227061da546Spatrick         size_t num_threads = threads.GetSize();
5228061da546Spatrick         item.Resize(num_threads, t);
5229061da546Spatrick         for (size_t i = 0; i < num_threads; ++i) {
5230be691f3bSpatrick           ThreadSP thread = threads.GetThreadAtIndex(i);
5231be691f3bSpatrick           item[i].SetIdentifier(thread->GetID());
5232061da546Spatrick           item[i].SetMightHaveChildren(true);
5233be691f3bSpatrick           if (selected_thread->GetID() == thread->GetID())
5234be691f3bSpatrick             item[i].Expand();
5235061da546Spatrick         }
5236061da546Spatrick         return;
5237061da546Spatrick       }
5238061da546Spatrick     }
5239061da546Spatrick     item.ClearChildren();
5240061da546Spatrick   }
5241061da546Spatrick 
TreeDelegateUpdateSelection(TreeItem & root,int & selection_index,TreeItem * & selected_item)5242be691f3bSpatrick   void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
5243be691f3bSpatrick                                    TreeItem *&selected_item) override {
5244be691f3bSpatrick     if (!m_update_selection)
5245be691f3bSpatrick       return;
5246be691f3bSpatrick 
5247be691f3bSpatrick     ProcessSP process_sp = GetProcess();
5248be691f3bSpatrick     if (!(process_sp && process_sp->IsAlive()))
5249be691f3bSpatrick       return;
5250be691f3bSpatrick 
5251be691f3bSpatrick     StateType state = process_sp->GetState();
5252be691f3bSpatrick     if (!StateIsStoppedState(state, true))
5253be691f3bSpatrick       return;
5254be691f3bSpatrick 
5255be691f3bSpatrick     ThreadList &threads = process_sp->GetThreadList();
5256be691f3bSpatrick     std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
5257be691f3bSpatrick     ThreadSP selected_thread = threads.GetSelectedThread();
5258be691f3bSpatrick     size_t num_threads = threads.GetSize();
5259be691f3bSpatrick     for (size_t i = 0; i < num_threads; ++i) {
5260be691f3bSpatrick       ThreadSP thread = threads.GetThreadAtIndex(i);
5261be691f3bSpatrick       if (selected_thread->GetID() == thread->GetID()) {
5262be691f3bSpatrick         selected_item = &root[i][thread->GetSelectedFrameIndex()];
5263be691f3bSpatrick         selection_index = selected_item->GetRowIndex();
5264be691f3bSpatrick         return;
5265be691f3bSpatrick       }
5266be691f3bSpatrick     }
5267be691f3bSpatrick   }
5268be691f3bSpatrick 
TreeDelegateItemSelected(TreeItem & item)5269061da546Spatrick   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5270061da546Spatrick 
TreeDelegateExpandRootByDefault()5271be691f3bSpatrick   bool TreeDelegateExpandRootByDefault() override { return true; }
5272be691f3bSpatrick 
5273061da546Spatrick protected:
5274061da546Spatrick   std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp;
5275061da546Spatrick   Debugger &m_debugger;
5276*f6aab3d8Srobert   uint32_t m_stop_id = UINT32_MAX;
5277*f6aab3d8Srobert   bool m_update_selection = false;
5278061da546Spatrick   FormatEntity::Entry m_format;
5279061da546Spatrick };
5280061da546Spatrick 
5281*f6aab3d8Srobert class BreakpointLocationTreeDelegate : public TreeDelegate {
5282*f6aab3d8Srobert public:
BreakpointLocationTreeDelegate(Debugger & debugger)5283*f6aab3d8Srobert   BreakpointLocationTreeDelegate(Debugger &debugger)
5284*f6aab3d8Srobert       : TreeDelegate(), m_debugger(debugger) {}
5285*f6aab3d8Srobert 
5286*f6aab3d8Srobert   ~BreakpointLocationTreeDelegate() override = default;
5287*f6aab3d8Srobert 
GetProcess()5288*f6aab3d8Srobert   Process *GetProcess() {
5289*f6aab3d8Srobert     ExecutionContext exe_ctx(
5290*f6aab3d8Srobert         m_debugger.GetCommandInterpreter().GetExecutionContext());
5291*f6aab3d8Srobert     return exe_ctx.GetProcessPtr();
5292*f6aab3d8Srobert   }
5293*f6aab3d8Srobert 
GetBreakpointLocation(const TreeItem & item)5294*f6aab3d8Srobert   BreakpointLocationSP GetBreakpointLocation(const TreeItem &item) {
5295*f6aab3d8Srobert     Breakpoint *breakpoint = (Breakpoint *)item.GetUserData();
5296*f6aab3d8Srobert     return breakpoint->GetLocationAtIndex(item.GetIdentifier());
5297*f6aab3d8Srobert   }
5298*f6aab3d8Srobert 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5299*f6aab3d8Srobert   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5300*f6aab3d8Srobert     BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item);
5301*f6aab3d8Srobert     Process *process = GetProcess();
5302*f6aab3d8Srobert     StreamString stream;
5303*f6aab3d8Srobert     stream.Printf("%i.%i: ", breakpoint_location->GetBreakpoint().GetID(),
5304*f6aab3d8Srobert                   breakpoint_location->GetID());
5305*f6aab3d8Srobert     Address address = breakpoint_location->GetAddress();
5306*f6aab3d8Srobert     address.Dump(&stream, process, Address::DumpStyleResolvedDescription,
5307*f6aab3d8Srobert                  Address::DumpStyleInvalid);
5308*f6aab3d8Srobert     window.PutCStringTruncated(1, stream.GetString().str().c_str());
5309*f6aab3d8Srobert   }
5310*f6aab3d8Srobert 
ComputeDetailsList(BreakpointLocationSP breakpoint_location)5311*f6aab3d8Srobert   StringList ComputeDetailsList(BreakpointLocationSP breakpoint_location) {
5312*f6aab3d8Srobert     StringList details;
5313*f6aab3d8Srobert 
5314*f6aab3d8Srobert     Address address = breakpoint_location->GetAddress();
5315*f6aab3d8Srobert     SymbolContext symbol_context;
5316*f6aab3d8Srobert     address.CalculateSymbolContext(&symbol_context);
5317*f6aab3d8Srobert 
5318*f6aab3d8Srobert     if (symbol_context.module_sp) {
5319*f6aab3d8Srobert       StreamString module_stream;
5320*f6aab3d8Srobert       module_stream.PutCString("module = ");
5321*f6aab3d8Srobert       symbol_context.module_sp->GetFileSpec().Dump(
5322*f6aab3d8Srobert           module_stream.AsRawOstream());
5323*f6aab3d8Srobert       details.AppendString(module_stream.GetString());
5324*f6aab3d8Srobert     }
5325*f6aab3d8Srobert 
5326*f6aab3d8Srobert     if (symbol_context.comp_unit != nullptr) {
5327*f6aab3d8Srobert       StreamString compile_unit_stream;
5328*f6aab3d8Srobert       compile_unit_stream.PutCString("compile unit = ");
5329*f6aab3d8Srobert       symbol_context.comp_unit->GetPrimaryFile().GetFilename().Dump(
5330*f6aab3d8Srobert           &compile_unit_stream);
5331*f6aab3d8Srobert       details.AppendString(compile_unit_stream.GetString());
5332*f6aab3d8Srobert 
5333*f6aab3d8Srobert       if (symbol_context.function != nullptr) {
5334*f6aab3d8Srobert         StreamString function_stream;
5335*f6aab3d8Srobert         function_stream.PutCString("function = ");
5336*f6aab3d8Srobert         function_stream.PutCString(
5337*f6aab3d8Srobert             symbol_context.function->GetName().AsCString("<unknown>"));
5338*f6aab3d8Srobert         details.AppendString(function_stream.GetString());
5339*f6aab3d8Srobert       }
5340*f6aab3d8Srobert 
5341*f6aab3d8Srobert       if (symbol_context.line_entry.line > 0) {
5342*f6aab3d8Srobert         StreamString location_stream;
5343*f6aab3d8Srobert         location_stream.PutCString("location = ");
5344*f6aab3d8Srobert         symbol_context.line_entry.DumpStopContext(&location_stream, true);
5345*f6aab3d8Srobert         details.AppendString(location_stream.GetString());
5346*f6aab3d8Srobert       }
5347*f6aab3d8Srobert 
5348*f6aab3d8Srobert     } else {
5349*f6aab3d8Srobert       if (symbol_context.symbol) {
5350*f6aab3d8Srobert         StreamString symbol_stream;
5351*f6aab3d8Srobert         if (breakpoint_location->IsReExported())
5352*f6aab3d8Srobert           symbol_stream.PutCString("re-exported target = ");
5353*f6aab3d8Srobert         else
5354*f6aab3d8Srobert           symbol_stream.PutCString("symbol = ");
5355*f6aab3d8Srobert         symbol_stream.PutCString(
5356*f6aab3d8Srobert             symbol_context.symbol->GetName().AsCString("<unknown>"));
5357*f6aab3d8Srobert         details.AppendString(symbol_stream.GetString());
5358*f6aab3d8Srobert       }
5359*f6aab3d8Srobert     }
5360*f6aab3d8Srobert 
5361*f6aab3d8Srobert     Process *process = GetProcess();
5362*f6aab3d8Srobert 
5363*f6aab3d8Srobert     StreamString address_stream;
5364*f6aab3d8Srobert     address.Dump(&address_stream, process, Address::DumpStyleLoadAddress,
5365*f6aab3d8Srobert                  Address::DumpStyleModuleWithFileAddress);
5366*f6aab3d8Srobert     details.AppendString(address_stream.GetString());
5367*f6aab3d8Srobert 
5368*f6aab3d8Srobert     BreakpointSiteSP breakpoint_site = breakpoint_location->GetBreakpointSite();
5369*f6aab3d8Srobert     if (breakpoint_location->IsIndirect() && breakpoint_site) {
5370*f6aab3d8Srobert       Address resolved_address;
5371*f6aab3d8Srobert       resolved_address.SetLoadAddress(breakpoint_site->GetLoadAddress(),
5372*f6aab3d8Srobert                                       &breakpoint_location->GetTarget());
5373*f6aab3d8Srobert       Symbol *resolved_symbol = resolved_address.CalculateSymbolContextSymbol();
5374*f6aab3d8Srobert       if (resolved_symbol) {
5375*f6aab3d8Srobert         StreamString indirect_target_stream;
5376*f6aab3d8Srobert         indirect_target_stream.PutCString("indirect target = ");
5377*f6aab3d8Srobert         indirect_target_stream.PutCString(
5378*f6aab3d8Srobert             resolved_symbol->GetName().GetCString());
5379*f6aab3d8Srobert         details.AppendString(indirect_target_stream.GetString());
5380*f6aab3d8Srobert       }
5381*f6aab3d8Srobert     }
5382*f6aab3d8Srobert 
5383*f6aab3d8Srobert     bool is_resolved = breakpoint_location->IsResolved();
5384*f6aab3d8Srobert     StreamString resolved_stream;
5385*f6aab3d8Srobert     resolved_stream.Printf("resolved = %s", is_resolved ? "true" : "false");
5386*f6aab3d8Srobert     details.AppendString(resolved_stream.GetString());
5387*f6aab3d8Srobert 
5388*f6aab3d8Srobert     bool is_hardware = is_resolved && breakpoint_site->IsHardware();
5389*f6aab3d8Srobert     StreamString hardware_stream;
5390*f6aab3d8Srobert     hardware_stream.Printf("hardware = %s", is_hardware ? "true" : "false");
5391*f6aab3d8Srobert     details.AppendString(hardware_stream.GetString());
5392*f6aab3d8Srobert 
5393*f6aab3d8Srobert     StreamString hit_count_stream;
5394*f6aab3d8Srobert     hit_count_stream.Printf("hit count = %-4u",
5395*f6aab3d8Srobert                             breakpoint_location->GetHitCount());
5396*f6aab3d8Srobert     details.AppendString(hit_count_stream.GetString());
5397*f6aab3d8Srobert 
5398*f6aab3d8Srobert     return details;
5399*f6aab3d8Srobert   }
5400*f6aab3d8Srobert 
TreeDelegateGenerateChildren(TreeItem & item)5401*f6aab3d8Srobert   void TreeDelegateGenerateChildren(TreeItem &item) override {
5402*f6aab3d8Srobert     BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item);
5403*f6aab3d8Srobert     StringList details = ComputeDetailsList(breakpoint_location);
5404*f6aab3d8Srobert 
5405*f6aab3d8Srobert     if (!m_string_delegate_sp)
5406*f6aab3d8Srobert       m_string_delegate_sp = std::make_shared<TextTreeDelegate>();
5407*f6aab3d8Srobert     TreeItem details_tree_item(&item, *m_string_delegate_sp, false);
5408*f6aab3d8Srobert 
5409*f6aab3d8Srobert     item.Resize(details.GetSize(), details_tree_item);
5410*f6aab3d8Srobert     for (size_t i = 0; i < details.GetSize(); i++) {
5411*f6aab3d8Srobert       item[i].SetText(details.GetStringAtIndex(i));
5412*f6aab3d8Srobert     }
5413*f6aab3d8Srobert   }
5414*f6aab3d8Srobert 
TreeDelegateItemSelected(TreeItem & item)5415*f6aab3d8Srobert   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5416*f6aab3d8Srobert 
5417*f6aab3d8Srobert protected:
5418*f6aab3d8Srobert   Debugger &m_debugger;
5419*f6aab3d8Srobert   std::shared_ptr<TextTreeDelegate> m_string_delegate_sp;
5420*f6aab3d8Srobert };
5421*f6aab3d8Srobert 
5422*f6aab3d8Srobert class BreakpointTreeDelegate : public TreeDelegate {
5423*f6aab3d8Srobert public:
BreakpointTreeDelegate(Debugger & debugger)5424*f6aab3d8Srobert   BreakpointTreeDelegate(Debugger &debugger)
5425*f6aab3d8Srobert       : TreeDelegate(), m_debugger(debugger),
5426*f6aab3d8Srobert         m_breakpoint_location_delegate_sp() {}
5427*f6aab3d8Srobert 
5428*f6aab3d8Srobert   ~BreakpointTreeDelegate() override = default;
5429*f6aab3d8Srobert 
GetBreakpoint(const TreeItem & item)5430*f6aab3d8Srobert   BreakpointSP GetBreakpoint(const TreeItem &item) {
5431*f6aab3d8Srobert     TargetSP target = m_debugger.GetSelectedTarget();
5432*f6aab3d8Srobert     BreakpointList &breakpoints = target->GetBreakpointList(false);
5433*f6aab3d8Srobert     return breakpoints.GetBreakpointAtIndex(item.GetIdentifier());
5434*f6aab3d8Srobert   }
5435*f6aab3d8Srobert 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5436*f6aab3d8Srobert   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5437*f6aab3d8Srobert     BreakpointSP breakpoint = GetBreakpoint(item);
5438*f6aab3d8Srobert     StreamString stream;
5439*f6aab3d8Srobert     stream.Format("{0}: ", breakpoint->GetID());
5440*f6aab3d8Srobert     breakpoint->GetResolverDescription(&stream);
5441*f6aab3d8Srobert     breakpoint->GetFilterDescription(&stream);
5442*f6aab3d8Srobert     window.PutCStringTruncated(1, stream.GetString().str().c_str());
5443*f6aab3d8Srobert   }
5444*f6aab3d8Srobert 
TreeDelegateGenerateChildren(TreeItem & item)5445*f6aab3d8Srobert   void TreeDelegateGenerateChildren(TreeItem &item) override {
5446*f6aab3d8Srobert     BreakpointSP breakpoint = GetBreakpoint(item);
5447*f6aab3d8Srobert 
5448*f6aab3d8Srobert     if (!m_breakpoint_location_delegate_sp)
5449*f6aab3d8Srobert       m_breakpoint_location_delegate_sp =
5450*f6aab3d8Srobert           std::make_shared<BreakpointLocationTreeDelegate>(m_debugger);
5451*f6aab3d8Srobert     TreeItem breakpoint_location_tree_item(
5452*f6aab3d8Srobert         &item, *m_breakpoint_location_delegate_sp, true);
5453*f6aab3d8Srobert 
5454*f6aab3d8Srobert     item.Resize(breakpoint->GetNumLocations(), breakpoint_location_tree_item);
5455*f6aab3d8Srobert     for (size_t i = 0; i < breakpoint->GetNumLocations(); i++) {
5456*f6aab3d8Srobert       item[i].SetIdentifier(i);
5457*f6aab3d8Srobert       item[i].SetUserData(breakpoint.get());
5458*f6aab3d8Srobert     }
5459*f6aab3d8Srobert   }
5460*f6aab3d8Srobert 
TreeDelegateItemSelected(TreeItem & item)5461*f6aab3d8Srobert   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5462*f6aab3d8Srobert 
5463*f6aab3d8Srobert protected:
5464*f6aab3d8Srobert   Debugger &m_debugger;
5465*f6aab3d8Srobert   std::shared_ptr<BreakpointLocationTreeDelegate>
5466*f6aab3d8Srobert       m_breakpoint_location_delegate_sp;
5467*f6aab3d8Srobert };
5468*f6aab3d8Srobert 
5469*f6aab3d8Srobert class BreakpointsTreeDelegate : public TreeDelegate {
5470*f6aab3d8Srobert public:
BreakpointsTreeDelegate(Debugger & debugger)5471*f6aab3d8Srobert   BreakpointsTreeDelegate(Debugger &debugger)
5472*f6aab3d8Srobert       : TreeDelegate(), m_debugger(debugger), m_breakpoint_delegate_sp() {}
5473*f6aab3d8Srobert 
5474*f6aab3d8Srobert   ~BreakpointsTreeDelegate() override = default;
5475*f6aab3d8Srobert 
TreeDelegateShouldDraw()5476*f6aab3d8Srobert   bool TreeDelegateShouldDraw() override {
5477*f6aab3d8Srobert     TargetSP target = m_debugger.GetSelectedTarget();
5478*f6aab3d8Srobert     if (!target)
5479*f6aab3d8Srobert       return false;
5480*f6aab3d8Srobert 
5481*f6aab3d8Srobert     return true;
5482*f6aab3d8Srobert   }
5483*f6aab3d8Srobert 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5484*f6aab3d8Srobert   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5485*f6aab3d8Srobert     window.PutCString("Breakpoints");
5486*f6aab3d8Srobert   }
5487*f6aab3d8Srobert 
TreeDelegateGenerateChildren(TreeItem & item)5488*f6aab3d8Srobert   void TreeDelegateGenerateChildren(TreeItem &item) override {
5489*f6aab3d8Srobert     TargetSP target = m_debugger.GetSelectedTarget();
5490*f6aab3d8Srobert 
5491*f6aab3d8Srobert     BreakpointList &breakpoints = target->GetBreakpointList(false);
5492*f6aab3d8Srobert     std::unique_lock<std::recursive_mutex> lock;
5493*f6aab3d8Srobert     breakpoints.GetListMutex(lock);
5494*f6aab3d8Srobert 
5495*f6aab3d8Srobert     if (!m_breakpoint_delegate_sp)
5496*f6aab3d8Srobert       m_breakpoint_delegate_sp =
5497*f6aab3d8Srobert           std::make_shared<BreakpointTreeDelegate>(m_debugger);
5498*f6aab3d8Srobert     TreeItem breakpoint_tree_item(&item, *m_breakpoint_delegate_sp, true);
5499*f6aab3d8Srobert 
5500*f6aab3d8Srobert     item.Resize(breakpoints.GetSize(), breakpoint_tree_item);
5501*f6aab3d8Srobert     for (size_t i = 0; i < breakpoints.GetSize(); i++) {
5502*f6aab3d8Srobert       item[i].SetIdentifier(i);
5503*f6aab3d8Srobert     }
5504*f6aab3d8Srobert   }
5505*f6aab3d8Srobert 
TreeDelegateItemSelected(TreeItem & item)5506*f6aab3d8Srobert   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5507*f6aab3d8Srobert 
TreeDelegateExpandRootByDefault()5508*f6aab3d8Srobert   bool TreeDelegateExpandRootByDefault() override { return true; }
5509*f6aab3d8Srobert 
5510*f6aab3d8Srobert protected:
5511*f6aab3d8Srobert   Debugger &m_debugger;
5512*f6aab3d8Srobert   std::shared_ptr<BreakpointTreeDelegate> m_breakpoint_delegate_sp;
5513*f6aab3d8Srobert };
5514*f6aab3d8Srobert 
5515061da546Spatrick class ValueObjectListDelegate : public WindowDelegate {
5516061da546Spatrick public:
ValueObjectListDelegate()5517be691f3bSpatrick   ValueObjectListDelegate() : m_rows() {}
5518061da546Spatrick 
ValueObjectListDelegate(ValueObjectList & valobj_list)5519*f6aab3d8Srobert   ValueObjectListDelegate(ValueObjectList &valobj_list) : m_rows() {
5520061da546Spatrick     SetValues(valobj_list);
5521061da546Spatrick   }
5522061da546Spatrick 
5523061da546Spatrick   ~ValueObjectListDelegate() override = default;
5524061da546Spatrick 
SetValues(ValueObjectList & valobj_list)5525061da546Spatrick   void SetValues(ValueObjectList &valobj_list) {
5526061da546Spatrick     m_selected_row = nullptr;
5527061da546Spatrick     m_selected_row_idx = 0;
5528061da546Spatrick     m_first_visible_row = 0;
5529061da546Spatrick     m_num_rows = 0;
5530061da546Spatrick     m_rows.clear();
5531061da546Spatrick     for (auto &valobj_sp : valobj_list.GetObjects())
5532061da546Spatrick       m_rows.push_back(Row(valobj_sp, nullptr));
5533061da546Spatrick   }
5534061da546Spatrick 
WindowDelegateDraw(Window & window,bool force)5535061da546Spatrick   bool WindowDelegateDraw(Window &window, bool force) override {
5536061da546Spatrick     m_num_rows = 0;
5537061da546Spatrick     m_min_x = 2;
5538061da546Spatrick     m_min_y = 1;
5539061da546Spatrick     m_max_x = window.GetWidth() - 1;
5540061da546Spatrick     m_max_y = window.GetHeight() - 1;
5541061da546Spatrick 
5542061da546Spatrick     window.Erase();
5543061da546Spatrick     window.DrawTitleBox(window.GetName());
5544061da546Spatrick 
5545061da546Spatrick     const int num_visible_rows = NumVisibleRows();
5546061da546Spatrick     const int num_rows = CalculateTotalNumberRows(m_rows);
5547061da546Spatrick 
5548061da546Spatrick     // If we unexpanded while having something selected our total number of
5549061da546Spatrick     // rows is less than the num visible rows, then make sure we show all the
5550061da546Spatrick     // rows by setting the first visible row accordingly.
5551061da546Spatrick     if (m_first_visible_row > 0 && num_rows < num_visible_rows)
5552061da546Spatrick       m_first_visible_row = 0;
5553061da546Spatrick 
5554061da546Spatrick     // Make sure the selected row is always visible
5555061da546Spatrick     if (m_selected_row_idx < m_first_visible_row)
5556061da546Spatrick       m_first_visible_row = m_selected_row_idx;
5557061da546Spatrick     else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
5558061da546Spatrick       m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
5559061da546Spatrick 
5560061da546Spatrick     DisplayRows(window, m_rows, g_options);
5561061da546Spatrick 
5562061da546Spatrick     // Get the selected row
5563061da546Spatrick     m_selected_row = GetRowForRowIndex(m_selected_row_idx);
5564061da546Spatrick     // Keep the cursor on the selected row so the highlight and the cursor are
5565061da546Spatrick     // always on the same line
5566061da546Spatrick     if (m_selected_row)
5567061da546Spatrick       window.MoveCursor(m_selected_row->x, m_selected_row->y);
5568061da546Spatrick 
5569061da546Spatrick     return true; // Drawing handled
5570061da546Spatrick   }
5571061da546Spatrick 
WindowDelegateGetKeyHelp()5572061da546Spatrick   KeyHelp *WindowDelegateGetKeyHelp() override {
5573061da546Spatrick     static curses::KeyHelp g_source_view_key_help[] = {
5574061da546Spatrick         {KEY_UP, "Select previous item"},
5575061da546Spatrick         {KEY_DOWN, "Select next item"},
5576061da546Spatrick         {KEY_RIGHT, "Expand selected item"},
5577061da546Spatrick         {KEY_LEFT, "Unexpand selected item or select parent if not expanded"},
5578061da546Spatrick         {KEY_PPAGE, "Page up"},
5579061da546Spatrick         {KEY_NPAGE, "Page down"},
5580061da546Spatrick         {'A', "Format as annotated address"},
5581061da546Spatrick         {'b', "Format as binary"},
5582061da546Spatrick         {'B', "Format as hex bytes with ASCII"},
5583061da546Spatrick         {'c', "Format as character"},
5584061da546Spatrick         {'d', "Format as a signed integer"},
5585061da546Spatrick         {'D', "Format selected value using the default format for the type"},
5586061da546Spatrick         {'f', "Format as float"},
5587061da546Spatrick         {'h', "Show help dialog"},
5588061da546Spatrick         {'i', "Format as instructions"},
5589061da546Spatrick         {'o', "Format as octal"},
5590061da546Spatrick         {'p', "Format as pointer"},
5591061da546Spatrick         {'s', "Format as C string"},
5592061da546Spatrick         {'t', "Toggle showing/hiding type names"},
5593061da546Spatrick         {'u', "Format as an unsigned integer"},
5594061da546Spatrick         {'x', "Format as hex"},
5595061da546Spatrick         {'X', "Format as uppercase hex"},
5596061da546Spatrick         {' ', "Toggle item expansion"},
5597061da546Spatrick         {',', "Page up"},
5598061da546Spatrick         {'.', "Page down"},
5599061da546Spatrick         {'\0', nullptr}};
5600061da546Spatrick     return g_source_view_key_help;
5601061da546Spatrick   }
5602061da546Spatrick 
WindowDelegateHandleChar(Window & window,int c)5603061da546Spatrick   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
5604061da546Spatrick     switch (c) {
5605061da546Spatrick     case 'x':
5606061da546Spatrick     case 'X':
5607061da546Spatrick     case 'o':
5608061da546Spatrick     case 's':
5609061da546Spatrick     case 'u':
5610061da546Spatrick     case 'd':
5611061da546Spatrick     case 'D':
5612061da546Spatrick     case 'i':
5613061da546Spatrick     case 'A':
5614061da546Spatrick     case 'p':
5615061da546Spatrick     case 'c':
5616061da546Spatrick     case 'b':
5617061da546Spatrick     case 'B':
5618061da546Spatrick     case 'f':
5619061da546Spatrick       // Change the format for the currently selected item
5620061da546Spatrick       if (m_selected_row) {
5621061da546Spatrick         auto valobj_sp = m_selected_row->value.GetSP();
5622061da546Spatrick         if (valobj_sp)
5623061da546Spatrick           valobj_sp->SetFormat(FormatForChar(c));
5624061da546Spatrick       }
5625061da546Spatrick       return eKeyHandled;
5626061da546Spatrick 
5627061da546Spatrick     case 't':
5628061da546Spatrick       // Toggle showing type names
5629061da546Spatrick       g_options.show_types = !g_options.show_types;
5630061da546Spatrick       return eKeyHandled;
5631061da546Spatrick 
5632061da546Spatrick     case ',':
5633061da546Spatrick     case KEY_PPAGE:
5634061da546Spatrick       // Page up key
5635061da546Spatrick       if (m_first_visible_row > 0) {
5636061da546Spatrick         if (static_cast<int>(m_first_visible_row) > m_max_y)
5637061da546Spatrick           m_first_visible_row -= m_max_y;
5638061da546Spatrick         else
5639061da546Spatrick           m_first_visible_row = 0;
5640061da546Spatrick         m_selected_row_idx = m_first_visible_row;
5641061da546Spatrick       }
5642061da546Spatrick       return eKeyHandled;
5643061da546Spatrick 
5644061da546Spatrick     case '.':
5645061da546Spatrick     case KEY_NPAGE:
5646061da546Spatrick       // Page down key
5647061da546Spatrick       if (m_num_rows > static_cast<size_t>(m_max_y)) {
5648061da546Spatrick         if (m_first_visible_row + m_max_y < m_num_rows) {
5649061da546Spatrick           m_first_visible_row += m_max_y;
5650061da546Spatrick           m_selected_row_idx = m_first_visible_row;
5651061da546Spatrick         }
5652061da546Spatrick       }
5653061da546Spatrick       return eKeyHandled;
5654061da546Spatrick 
5655061da546Spatrick     case KEY_UP:
5656061da546Spatrick       if (m_selected_row_idx > 0)
5657061da546Spatrick         --m_selected_row_idx;
5658061da546Spatrick       return eKeyHandled;
5659061da546Spatrick 
5660061da546Spatrick     case KEY_DOWN:
5661061da546Spatrick       if (m_selected_row_idx + 1 < m_num_rows)
5662061da546Spatrick         ++m_selected_row_idx;
5663061da546Spatrick       return eKeyHandled;
5664061da546Spatrick 
5665061da546Spatrick     case KEY_RIGHT:
5666061da546Spatrick       if (m_selected_row) {
5667061da546Spatrick         if (!m_selected_row->expanded)
5668061da546Spatrick           m_selected_row->Expand();
5669061da546Spatrick       }
5670061da546Spatrick       return eKeyHandled;
5671061da546Spatrick 
5672061da546Spatrick     case KEY_LEFT:
5673061da546Spatrick       if (m_selected_row) {
5674061da546Spatrick         if (m_selected_row->expanded)
5675061da546Spatrick           m_selected_row->Unexpand();
5676061da546Spatrick         else if (m_selected_row->parent)
5677061da546Spatrick           m_selected_row_idx = m_selected_row->parent->row_idx;
5678061da546Spatrick       }
5679061da546Spatrick       return eKeyHandled;
5680061da546Spatrick 
5681061da546Spatrick     case ' ':
5682061da546Spatrick       // Toggle expansion state when SPACE is pressed
5683061da546Spatrick       if (m_selected_row) {
5684061da546Spatrick         if (m_selected_row->expanded)
5685061da546Spatrick           m_selected_row->Unexpand();
5686061da546Spatrick         else
5687061da546Spatrick           m_selected_row->Expand();
5688061da546Spatrick       }
5689061da546Spatrick       return eKeyHandled;
5690061da546Spatrick 
5691061da546Spatrick     case 'h':
5692061da546Spatrick       window.CreateHelpSubwindow();
5693061da546Spatrick       return eKeyHandled;
5694061da546Spatrick 
5695061da546Spatrick     default:
5696061da546Spatrick       break;
5697061da546Spatrick     }
5698061da546Spatrick     return eKeyNotHandled;
5699061da546Spatrick   }
5700061da546Spatrick 
5701061da546Spatrick protected:
5702061da546Spatrick   std::vector<Row> m_rows;
5703be691f3bSpatrick   Row *m_selected_row = nullptr;
5704be691f3bSpatrick   uint32_t m_selected_row_idx = 0;
5705be691f3bSpatrick   uint32_t m_first_visible_row = 0;
5706be691f3bSpatrick   uint32_t m_num_rows = 0;
5707*f6aab3d8Srobert   int m_min_x = 0;
5708*f6aab3d8Srobert   int m_min_y = 0;
5709be691f3bSpatrick   int m_max_x = 0;
5710be691f3bSpatrick   int m_max_y = 0;
5711061da546Spatrick 
FormatForChar(int c)5712061da546Spatrick   static Format FormatForChar(int c) {
5713061da546Spatrick     switch (c) {
5714061da546Spatrick     case 'x':
5715061da546Spatrick       return eFormatHex;
5716061da546Spatrick     case 'X':
5717061da546Spatrick       return eFormatHexUppercase;
5718061da546Spatrick     case 'o':
5719061da546Spatrick       return eFormatOctal;
5720061da546Spatrick     case 's':
5721061da546Spatrick       return eFormatCString;
5722061da546Spatrick     case 'u':
5723061da546Spatrick       return eFormatUnsigned;
5724061da546Spatrick     case 'd':
5725061da546Spatrick       return eFormatDecimal;
5726061da546Spatrick     case 'D':
5727061da546Spatrick       return eFormatDefault;
5728061da546Spatrick     case 'i':
5729061da546Spatrick       return eFormatInstruction;
5730061da546Spatrick     case 'A':
5731061da546Spatrick       return eFormatAddressInfo;
5732061da546Spatrick     case 'p':
5733061da546Spatrick       return eFormatPointer;
5734061da546Spatrick     case 'c':
5735061da546Spatrick       return eFormatChar;
5736061da546Spatrick     case 'b':
5737061da546Spatrick       return eFormatBinary;
5738061da546Spatrick     case 'B':
5739061da546Spatrick       return eFormatBytesWithASCII;
5740061da546Spatrick     case 'f':
5741061da546Spatrick       return eFormatFloat;
5742061da546Spatrick     }
5743061da546Spatrick     return eFormatDefault;
5744061da546Spatrick   }
5745061da546Spatrick 
DisplayRowObject(Window & window,Row & row,DisplayOptions & options,bool highlight,bool last_child)5746061da546Spatrick   bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options,
5747061da546Spatrick                         bool highlight, bool last_child) {
5748061da546Spatrick     ValueObject *valobj = row.value.GetSP().get();
5749061da546Spatrick 
5750061da546Spatrick     if (valobj == nullptr)
5751061da546Spatrick       return false;
5752061da546Spatrick 
5753061da546Spatrick     const char *type_name =
5754061da546Spatrick         options.show_types ? valobj->GetTypeName().GetCString() : nullptr;
5755061da546Spatrick     const char *name = valobj->GetName().GetCString();
5756061da546Spatrick     const char *value = valobj->GetValueAsCString();
5757061da546Spatrick     const char *summary = valobj->GetSummaryAsCString();
5758061da546Spatrick 
5759061da546Spatrick     window.MoveCursor(row.x, row.y);
5760061da546Spatrick 
5761061da546Spatrick     row.DrawTree(window);
5762061da546Spatrick 
5763061da546Spatrick     if (highlight)
5764061da546Spatrick       window.AttributeOn(A_REVERSE);
5765061da546Spatrick 
5766061da546Spatrick     if (type_name && type_name[0])
5767be691f3bSpatrick       window.PrintfTruncated(1, "(%s) ", type_name);
5768061da546Spatrick 
5769061da546Spatrick     if (name && name[0])
5770be691f3bSpatrick       window.PutCStringTruncated(1, name);
5771061da546Spatrick 
5772061da546Spatrick     attr_t changd_attr = 0;
5773061da546Spatrick     if (valobj->GetValueDidChange())
5774be691f3bSpatrick       changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD;
5775061da546Spatrick 
5776061da546Spatrick     if (value && value[0]) {
5777be691f3bSpatrick       window.PutCStringTruncated(1, " = ");
5778061da546Spatrick       if (changd_attr)
5779061da546Spatrick         window.AttributeOn(changd_attr);
5780be691f3bSpatrick       window.PutCStringTruncated(1, value);
5781061da546Spatrick       if (changd_attr)
5782061da546Spatrick         window.AttributeOff(changd_attr);
5783061da546Spatrick     }
5784061da546Spatrick 
5785061da546Spatrick     if (summary && summary[0]) {
5786be691f3bSpatrick       window.PutCStringTruncated(1, " ");
5787061da546Spatrick       if (changd_attr)
5788061da546Spatrick         window.AttributeOn(changd_attr);
5789be691f3bSpatrick       window.PutCStringTruncated(1, summary);
5790061da546Spatrick       if (changd_attr)
5791061da546Spatrick         window.AttributeOff(changd_attr);
5792061da546Spatrick     }
5793061da546Spatrick 
5794061da546Spatrick     if (highlight)
5795061da546Spatrick       window.AttributeOff(A_REVERSE);
5796061da546Spatrick 
5797061da546Spatrick     return true;
5798061da546Spatrick   }
5799061da546Spatrick 
DisplayRows(Window & window,std::vector<Row> & rows,DisplayOptions & options)5800061da546Spatrick   void DisplayRows(Window &window, std::vector<Row> &rows,
5801061da546Spatrick                    DisplayOptions &options) {
5802061da546Spatrick     // >   0x25B7
5803061da546Spatrick     // \/  0x25BD
5804061da546Spatrick 
5805061da546Spatrick     bool window_is_active = window.IsActive();
5806061da546Spatrick     for (auto &row : rows) {
5807061da546Spatrick       const bool last_child = row.parent && &rows[rows.size() - 1] == &row;
5808061da546Spatrick       // Save the row index in each Row structure
5809061da546Spatrick       row.row_idx = m_num_rows;
5810061da546Spatrick       if ((m_num_rows >= m_first_visible_row) &&
5811061da546Spatrick           ((m_num_rows - m_first_visible_row) <
5812061da546Spatrick            static_cast<size_t>(NumVisibleRows()))) {
5813061da546Spatrick         row.x = m_min_x;
5814061da546Spatrick         row.y = m_num_rows - m_first_visible_row + 1;
5815061da546Spatrick         if (DisplayRowObject(window, row, options,
5816061da546Spatrick                              window_is_active &&
5817061da546Spatrick                                  m_num_rows == m_selected_row_idx,
5818061da546Spatrick                              last_child)) {
5819061da546Spatrick           ++m_num_rows;
5820061da546Spatrick         } else {
5821061da546Spatrick           row.x = 0;
5822061da546Spatrick           row.y = 0;
5823061da546Spatrick         }
5824061da546Spatrick       } else {
5825061da546Spatrick         row.x = 0;
5826061da546Spatrick         row.y = 0;
5827061da546Spatrick         ++m_num_rows;
5828061da546Spatrick       }
5829061da546Spatrick 
5830*f6aab3d8Srobert       if (row.expanded) {
5831061da546Spatrick         auto &children = row.GetChildren();
5832*f6aab3d8Srobert         if (!children.empty()) {
5833061da546Spatrick           DisplayRows(window, children, options);
5834061da546Spatrick         }
5835061da546Spatrick       }
5836061da546Spatrick     }
5837*f6aab3d8Srobert   }
5838061da546Spatrick 
CalculateTotalNumberRows(std::vector<Row> & rows)5839061da546Spatrick   int CalculateTotalNumberRows(std::vector<Row> &rows) {
5840061da546Spatrick     int row_count = 0;
5841061da546Spatrick     for (auto &row : rows) {
5842061da546Spatrick       ++row_count;
5843061da546Spatrick       if (row.expanded)
5844061da546Spatrick         row_count += CalculateTotalNumberRows(row.GetChildren());
5845061da546Spatrick     }
5846061da546Spatrick     return row_count;
5847061da546Spatrick   }
5848061da546Spatrick 
GetRowForRowIndexImpl(std::vector<Row> & rows,size_t & row_index)5849061da546Spatrick   static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) {
5850061da546Spatrick     for (auto &row : rows) {
5851061da546Spatrick       if (row_index == 0)
5852061da546Spatrick         return &row;
5853061da546Spatrick       else {
5854061da546Spatrick         --row_index;
5855*f6aab3d8Srobert         if (row.expanded) {
5856061da546Spatrick           auto &children = row.GetChildren();
5857*f6aab3d8Srobert           if (!children.empty()) {
5858061da546Spatrick             Row *result = GetRowForRowIndexImpl(children, row_index);
5859061da546Spatrick             if (result)
5860061da546Spatrick               return result;
5861061da546Spatrick           }
5862061da546Spatrick         }
5863061da546Spatrick       }
5864*f6aab3d8Srobert     }
5865061da546Spatrick     return nullptr;
5866061da546Spatrick   }
5867061da546Spatrick 
GetRowForRowIndex(size_t row_index)5868061da546Spatrick   Row *GetRowForRowIndex(size_t row_index) {
5869061da546Spatrick     return GetRowForRowIndexImpl(m_rows, row_index);
5870061da546Spatrick   }
5871061da546Spatrick 
NumVisibleRows() const5872061da546Spatrick   int NumVisibleRows() const { return m_max_y - m_min_y; }
5873061da546Spatrick 
5874061da546Spatrick   static DisplayOptions g_options;
5875061da546Spatrick };
5876061da546Spatrick 
5877061da546Spatrick class FrameVariablesWindowDelegate : public ValueObjectListDelegate {
5878061da546Spatrick public:
FrameVariablesWindowDelegate(Debugger & debugger)5879061da546Spatrick   FrameVariablesWindowDelegate(Debugger &debugger)
5880*f6aab3d8Srobert       : ValueObjectListDelegate(), m_debugger(debugger) {}
5881061da546Spatrick 
5882061da546Spatrick   ~FrameVariablesWindowDelegate() override = default;
5883061da546Spatrick 
WindowDelegateGetHelpText()5884061da546Spatrick   const char *WindowDelegateGetHelpText() override {
5885061da546Spatrick     return "Frame variable window keyboard shortcuts:";
5886061da546Spatrick   }
5887061da546Spatrick 
WindowDelegateDraw(Window & window,bool force)5888061da546Spatrick   bool WindowDelegateDraw(Window &window, bool force) override {
5889061da546Spatrick     ExecutionContext exe_ctx(
5890061da546Spatrick         m_debugger.GetCommandInterpreter().GetExecutionContext());
5891061da546Spatrick     Process *process = exe_ctx.GetProcessPtr();
5892061da546Spatrick     Block *frame_block = nullptr;
5893061da546Spatrick     StackFrame *frame = nullptr;
5894061da546Spatrick 
5895061da546Spatrick     if (process) {
5896061da546Spatrick       StateType state = process->GetState();
5897061da546Spatrick       if (StateIsStoppedState(state, true)) {
5898061da546Spatrick         frame = exe_ctx.GetFramePtr();
5899061da546Spatrick         if (frame)
5900061da546Spatrick           frame_block = frame->GetFrameBlock();
5901061da546Spatrick       } else if (StateIsRunningState(state)) {
5902061da546Spatrick         return true; // Don't do any updating when we are running
5903061da546Spatrick       }
5904061da546Spatrick     }
5905061da546Spatrick 
5906061da546Spatrick     ValueObjectList local_values;
5907061da546Spatrick     if (frame_block) {
5908061da546Spatrick       // Only update the variables if they have changed
5909061da546Spatrick       if (m_frame_block != frame_block) {
5910061da546Spatrick         m_frame_block = frame_block;
5911061da546Spatrick 
5912*f6aab3d8Srobert         VariableList *locals = frame->GetVariableList(true, nullptr);
5913061da546Spatrick         if (locals) {
5914061da546Spatrick           const DynamicValueType use_dynamic = eDynamicDontRunTarget;
5915061da546Spatrick           for (const VariableSP &local_sp : *locals) {
5916061da546Spatrick             ValueObjectSP value_sp =
5917061da546Spatrick                 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic);
5918061da546Spatrick             if (value_sp) {
5919061da546Spatrick               ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue();
5920061da546Spatrick               if (synthetic_value_sp)
5921061da546Spatrick                 local_values.Append(synthetic_value_sp);
5922061da546Spatrick               else
5923061da546Spatrick                 local_values.Append(value_sp);
5924061da546Spatrick             }
5925061da546Spatrick           }
5926061da546Spatrick           // Update the values
5927061da546Spatrick           SetValues(local_values);
5928061da546Spatrick         }
5929061da546Spatrick       }
5930061da546Spatrick     } else {
5931061da546Spatrick       m_frame_block = nullptr;
5932061da546Spatrick       // Update the values with an empty list if there is no frame
5933061da546Spatrick       SetValues(local_values);
5934061da546Spatrick     }
5935061da546Spatrick 
5936061da546Spatrick     return ValueObjectListDelegate::WindowDelegateDraw(window, force);
5937061da546Spatrick   }
5938061da546Spatrick 
5939061da546Spatrick protected:
5940061da546Spatrick   Debugger &m_debugger;
5941*f6aab3d8Srobert   Block *m_frame_block = nullptr;
5942061da546Spatrick };
5943061da546Spatrick 
5944061da546Spatrick class RegistersWindowDelegate : public ValueObjectListDelegate {
5945061da546Spatrick public:
RegistersWindowDelegate(Debugger & debugger)5946061da546Spatrick   RegistersWindowDelegate(Debugger &debugger)
5947061da546Spatrick       : ValueObjectListDelegate(), m_debugger(debugger) {}
5948061da546Spatrick 
5949061da546Spatrick   ~RegistersWindowDelegate() override = default;
5950061da546Spatrick 
WindowDelegateGetHelpText()5951061da546Spatrick   const char *WindowDelegateGetHelpText() override {
5952061da546Spatrick     return "Register window keyboard shortcuts:";
5953061da546Spatrick   }
5954061da546Spatrick 
WindowDelegateDraw(Window & window,bool force)5955061da546Spatrick   bool WindowDelegateDraw(Window &window, bool force) override {
5956061da546Spatrick     ExecutionContext exe_ctx(
5957061da546Spatrick         m_debugger.GetCommandInterpreter().GetExecutionContext());
5958061da546Spatrick     StackFrame *frame = exe_ctx.GetFramePtr();
5959061da546Spatrick 
5960061da546Spatrick     ValueObjectList value_list;
5961061da546Spatrick     if (frame) {
5962061da546Spatrick       if (frame->GetStackID() != m_stack_id) {
5963061da546Spatrick         m_stack_id = frame->GetStackID();
5964061da546Spatrick         RegisterContextSP reg_ctx(frame->GetRegisterContext());
5965061da546Spatrick         if (reg_ctx) {
5966061da546Spatrick           const uint32_t num_sets = reg_ctx->GetRegisterSetCount();
5967061da546Spatrick           for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) {
5968061da546Spatrick             value_list.Append(
5969061da546Spatrick                 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx));
5970061da546Spatrick           }
5971061da546Spatrick         }
5972061da546Spatrick         SetValues(value_list);
5973061da546Spatrick       }
5974061da546Spatrick     } else {
5975061da546Spatrick       Process *process = exe_ctx.GetProcessPtr();
5976061da546Spatrick       if (process && process->IsAlive())
5977061da546Spatrick         return true; // Don't do any updating if we are running
5978061da546Spatrick       else {
5979061da546Spatrick         // Update the values with an empty list if there is no process or the
5980061da546Spatrick         // process isn't alive anymore
5981061da546Spatrick         SetValues(value_list);
5982061da546Spatrick       }
5983061da546Spatrick     }
5984061da546Spatrick     return ValueObjectListDelegate::WindowDelegateDraw(window, force);
5985061da546Spatrick   }
5986061da546Spatrick 
5987061da546Spatrick protected:
5988061da546Spatrick   Debugger &m_debugger;
5989061da546Spatrick   StackID m_stack_id;
5990061da546Spatrick };
5991061da546Spatrick 
CursesKeyToCString(int ch)5992061da546Spatrick static const char *CursesKeyToCString(int ch) {
5993061da546Spatrick   static char g_desc[32];
5994061da546Spatrick   if (ch >= KEY_F0 && ch < KEY_F0 + 64) {
5995061da546Spatrick     snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0);
5996061da546Spatrick     return g_desc;
5997061da546Spatrick   }
5998061da546Spatrick   switch (ch) {
5999061da546Spatrick   case KEY_DOWN:
6000061da546Spatrick     return "down";
6001061da546Spatrick   case KEY_UP:
6002061da546Spatrick     return "up";
6003061da546Spatrick   case KEY_LEFT:
6004061da546Spatrick     return "left";
6005061da546Spatrick   case KEY_RIGHT:
6006061da546Spatrick     return "right";
6007061da546Spatrick   case KEY_HOME:
6008061da546Spatrick     return "home";
6009061da546Spatrick   case KEY_BACKSPACE:
6010061da546Spatrick     return "backspace";
6011061da546Spatrick   case KEY_DL:
6012061da546Spatrick     return "delete-line";
6013061da546Spatrick   case KEY_IL:
6014061da546Spatrick     return "insert-line";
6015061da546Spatrick   case KEY_DC:
6016061da546Spatrick     return "delete-char";
6017061da546Spatrick   case KEY_IC:
6018061da546Spatrick     return "insert-char";
6019061da546Spatrick   case KEY_CLEAR:
6020061da546Spatrick     return "clear";
6021061da546Spatrick   case KEY_EOS:
6022061da546Spatrick     return "clear-to-eos";
6023061da546Spatrick   case KEY_EOL:
6024061da546Spatrick     return "clear-to-eol";
6025061da546Spatrick   case KEY_SF:
6026061da546Spatrick     return "scroll-forward";
6027061da546Spatrick   case KEY_SR:
6028061da546Spatrick     return "scroll-backward";
6029061da546Spatrick   case KEY_NPAGE:
6030061da546Spatrick     return "page-down";
6031061da546Spatrick   case KEY_PPAGE:
6032061da546Spatrick     return "page-up";
6033061da546Spatrick   case KEY_STAB:
6034061da546Spatrick     return "set-tab";
6035061da546Spatrick   case KEY_CTAB:
6036061da546Spatrick     return "clear-tab";
6037061da546Spatrick   case KEY_CATAB:
6038061da546Spatrick     return "clear-all-tabs";
6039061da546Spatrick   case KEY_ENTER:
6040061da546Spatrick     return "enter";
6041061da546Spatrick   case KEY_PRINT:
6042061da546Spatrick     return "print";
6043061da546Spatrick   case KEY_LL:
6044061da546Spatrick     return "lower-left key";
6045061da546Spatrick   case KEY_A1:
6046061da546Spatrick     return "upper left of keypad";
6047061da546Spatrick   case KEY_A3:
6048061da546Spatrick     return "upper right of keypad";
6049061da546Spatrick   case KEY_B2:
6050061da546Spatrick     return "center of keypad";
6051061da546Spatrick   case KEY_C1:
6052061da546Spatrick     return "lower left of keypad";
6053061da546Spatrick   case KEY_C3:
6054061da546Spatrick     return "lower right of keypad";
6055061da546Spatrick   case KEY_BTAB:
6056061da546Spatrick     return "back-tab key";
6057061da546Spatrick   case KEY_BEG:
6058061da546Spatrick     return "begin key";
6059061da546Spatrick   case KEY_CANCEL:
6060061da546Spatrick     return "cancel key";
6061061da546Spatrick   case KEY_CLOSE:
6062061da546Spatrick     return "close key";
6063061da546Spatrick   case KEY_COMMAND:
6064061da546Spatrick     return "command key";
6065061da546Spatrick   case KEY_COPY:
6066061da546Spatrick     return "copy key";
6067061da546Spatrick   case KEY_CREATE:
6068061da546Spatrick     return "create key";
6069061da546Spatrick   case KEY_END:
6070061da546Spatrick     return "end key";
6071061da546Spatrick   case KEY_EXIT:
6072061da546Spatrick     return "exit key";
6073061da546Spatrick   case KEY_FIND:
6074061da546Spatrick     return "find key";
6075061da546Spatrick   case KEY_HELP:
6076061da546Spatrick     return "help key";
6077061da546Spatrick   case KEY_MARK:
6078061da546Spatrick     return "mark key";
6079061da546Spatrick   case KEY_MESSAGE:
6080061da546Spatrick     return "message key";
6081061da546Spatrick   case KEY_MOVE:
6082061da546Spatrick     return "move key";
6083061da546Spatrick   case KEY_NEXT:
6084061da546Spatrick     return "next key";
6085061da546Spatrick   case KEY_OPEN:
6086061da546Spatrick     return "open key";
6087061da546Spatrick   case KEY_OPTIONS:
6088061da546Spatrick     return "options key";
6089061da546Spatrick   case KEY_PREVIOUS:
6090061da546Spatrick     return "previous key";
6091061da546Spatrick   case KEY_REDO:
6092061da546Spatrick     return "redo key";
6093061da546Spatrick   case KEY_REFERENCE:
6094061da546Spatrick     return "reference key";
6095061da546Spatrick   case KEY_REFRESH:
6096061da546Spatrick     return "refresh key";
6097061da546Spatrick   case KEY_REPLACE:
6098061da546Spatrick     return "replace key";
6099061da546Spatrick   case KEY_RESTART:
6100061da546Spatrick     return "restart key";
6101061da546Spatrick   case KEY_RESUME:
6102061da546Spatrick     return "resume key";
6103061da546Spatrick   case KEY_SAVE:
6104061da546Spatrick     return "save key";
6105061da546Spatrick   case KEY_SBEG:
6106061da546Spatrick     return "shifted begin key";
6107061da546Spatrick   case KEY_SCANCEL:
6108061da546Spatrick     return "shifted cancel key";
6109061da546Spatrick   case KEY_SCOMMAND:
6110061da546Spatrick     return "shifted command key";
6111061da546Spatrick   case KEY_SCOPY:
6112061da546Spatrick     return "shifted copy key";
6113061da546Spatrick   case KEY_SCREATE:
6114061da546Spatrick     return "shifted create key";
6115061da546Spatrick   case KEY_SDC:
6116061da546Spatrick     return "shifted delete-character key";
6117061da546Spatrick   case KEY_SDL:
6118061da546Spatrick     return "shifted delete-line key";
6119061da546Spatrick   case KEY_SELECT:
6120061da546Spatrick     return "select key";
6121061da546Spatrick   case KEY_SEND:
6122061da546Spatrick     return "shifted end key";
6123061da546Spatrick   case KEY_SEOL:
6124061da546Spatrick     return "shifted clear-to-end-of-line key";
6125061da546Spatrick   case KEY_SEXIT:
6126061da546Spatrick     return "shifted exit key";
6127061da546Spatrick   case KEY_SFIND:
6128061da546Spatrick     return "shifted find key";
6129061da546Spatrick   case KEY_SHELP:
6130061da546Spatrick     return "shifted help key";
6131061da546Spatrick   case KEY_SHOME:
6132061da546Spatrick     return "shifted home key";
6133061da546Spatrick   case KEY_SIC:
6134061da546Spatrick     return "shifted insert-character key";
6135061da546Spatrick   case KEY_SLEFT:
6136061da546Spatrick     return "shifted left-arrow key";
6137061da546Spatrick   case KEY_SMESSAGE:
6138061da546Spatrick     return "shifted message key";
6139061da546Spatrick   case KEY_SMOVE:
6140061da546Spatrick     return "shifted move key";
6141061da546Spatrick   case KEY_SNEXT:
6142061da546Spatrick     return "shifted next key";
6143061da546Spatrick   case KEY_SOPTIONS:
6144061da546Spatrick     return "shifted options key";
6145061da546Spatrick   case KEY_SPREVIOUS:
6146061da546Spatrick     return "shifted previous key";
6147061da546Spatrick   case KEY_SPRINT:
6148061da546Spatrick     return "shifted print key";
6149061da546Spatrick   case KEY_SREDO:
6150061da546Spatrick     return "shifted redo key";
6151061da546Spatrick   case KEY_SREPLACE:
6152061da546Spatrick     return "shifted replace key";
6153061da546Spatrick   case KEY_SRIGHT:
6154061da546Spatrick     return "shifted right-arrow key";
6155061da546Spatrick   case KEY_SRSUME:
6156061da546Spatrick     return "shifted resume key";
6157061da546Spatrick   case KEY_SSAVE:
6158061da546Spatrick     return "shifted save key";
6159061da546Spatrick   case KEY_SSUSPEND:
6160061da546Spatrick     return "shifted suspend key";
6161061da546Spatrick   case KEY_SUNDO:
6162061da546Spatrick     return "shifted undo key";
6163061da546Spatrick   case KEY_SUSPEND:
6164061da546Spatrick     return "suspend key";
6165061da546Spatrick   case KEY_UNDO:
6166061da546Spatrick     return "undo key";
6167061da546Spatrick   case KEY_MOUSE:
6168061da546Spatrick     return "Mouse event has occurred";
6169061da546Spatrick   case KEY_RESIZE:
6170061da546Spatrick     return "Terminal resize event";
6171061da546Spatrick #ifdef KEY_EVENT
6172061da546Spatrick   case KEY_EVENT:
6173061da546Spatrick     return "We were interrupted by an event";
6174061da546Spatrick #endif
6175061da546Spatrick   case KEY_RETURN:
6176061da546Spatrick     return "return";
6177061da546Spatrick   case ' ':
6178061da546Spatrick     return "space";
6179061da546Spatrick   case '\t':
6180061da546Spatrick     return "tab";
6181061da546Spatrick   case KEY_ESCAPE:
6182061da546Spatrick     return "escape";
6183061da546Spatrick   default:
6184dda28197Spatrick     if (llvm::isPrint(ch))
6185061da546Spatrick       snprintf(g_desc, sizeof(g_desc), "%c", ch);
6186061da546Spatrick     else
6187061da546Spatrick       snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch);
6188061da546Spatrick     return g_desc;
6189061da546Spatrick   }
6190061da546Spatrick   return nullptr;
6191061da546Spatrick }
6192061da546Spatrick 
HelpDialogDelegate(const char * text,KeyHelp * key_help_array)6193061da546Spatrick HelpDialogDelegate::HelpDialogDelegate(const char *text,
6194061da546Spatrick                                        KeyHelp *key_help_array)
6195*f6aab3d8Srobert     : m_text() {
6196061da546Spatrick   if (text && text[0]) {
6197061da546Spatrick     m_text.SplitIntoLines(text);
6198061da546Spatrick     m_text.AppendString("");
6199061da546Spatrick   }
6200061da546Spatrick   if (key_help_array) {
6201061da546Spatrick     for (KeyHelp *key = key_help_array; key->ch; ++key) {
6202061da546Spatrick       StreamString key_description;
6203061da546Spatrick       key_description.Printf("%10s - %s", CursesKeyToCString(key->ch),
6204061da546Spatrick                              key->description);
6205061da546Spatrick       m_text.AppendString(key_description.GetString());
6206061da546Spatrick     }
6207061da546Spatrick   }
6208061da546Spatrick }
6209061da546Spatrick 
6210061da546Spatrick HelpDialogDelegate::~HelpDialogDelegate() = default;
6211061da546Spatrick 
WindowDelegateDraw(Window & window,bool force)6212061da546Spatrick bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) {
6213061da546Spatrick   window.Erase();
6214061da546Spatrick   const int window_height = window.GetHeight();
6215061da546Spatrick   int x = 2;
6216061da546Spatrick   int y = 1;
6217061da546Spatrick   const int min_y = y;
6218061da546Spatrick   const int max_y = window_height - 1 - y;
6219061da546Spatrick   const size_t num_visible_lines = max_y - min_y + 1;
6220061da546Spatrick   const size_t num_lines = m_text.GetSize();
6221061da546Spatrick   const char *bottom_message;
6222061da546Spatrick   if (num_lines <= num_visible_lines)
6223061da546Spatrick     bottom_message = "Press any key to exit";
6224061da546Spatrick   else
6225061da546Spatrick     bottom_message = "Use arrows to scroll, any other key to exit";
6226061da546Spatrick   window.DrawTitleBox(window.GetName(), bottom_message);
6227061da546Spatrick   while (y <= max_y) {
6228061da546Spatrick     window.MoveCursor(x, y);
6229061da546Spatrick     window.PutCStringTruncated(
6230be691f3bSpatrick         1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y));
6231061da546Spatrick     ++y;
6232061da546Spatrick   }
6233061da546Spatrick   return true;
6234061da546Spatrick }
6235061da546Spatrick 
WindowDelegateHandleChar(Window & window,int key)6236061da546Spatrick HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window,
6237061da546Spatrick                                                               int key) {
6238061da546Spatrick   bool done = false;
6239061da546Spatrick   const size_t num_lines = m_text.GetSize();
6240061da546Spatrick   const size_t num_visible_lines = window.GetHeight() - 2;
6241061da546Spatrick 
6242061da546Spatrick   if (num_lines <= num_visible_lines) {
6243061da546Spatrick     done = true;
6244061da546Spatrick     // If we have all lines visible and don't need scrolling, then any key
6245061da546Spatrick     // press will cause us to exit
6246061da546Spatrick   } else {
6247061da546Spatrick     switch (key) {
6248061da546Spatrick     case KEY_UP:
6249061da546Spatrick       if (m_first_visible_line > 0)
6250061da546Spatrick         --m_first_visible_line;
6251061da546Spatrick       break;
6252061da546Spatrick 
6253061da546Spatrick     case KEY_DOWN:
6254061da546Spatrick       if (m_first_visible_line + num_visible_lines < num_lines)
6255061da546Spatrick         ++m_first_visible_line;
6256061da546Spatrick       break;
6257061da546Spatrick 
6258061da546Spatrick     case KEY_PPAGE:
6259061da546Spatrick     case ',':
6260061da546Spatrick       if (m_first_visible_line > 0) {
6261061da546Spatrick         if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines)
6262061da546Spatrick           m_first_visible_line -= num_visible_lines;
6263061da546Spatrick         else
6264061da546Spatrick           m_first_visible_line = 0;
6265061da546Spatrick       }
6266061da546Spatrick       break;
6267061da546Spatrick 
6268061da546Spatrick     case KEY_NPAGE:
6269061da546Spatrick     case '.':
6270061da546Spatrick       if (m_first_visible_line + num_visible_lines < num_lines) {
6271061da546Spatrick         m_first_visible_line += num_visible_lines;
6272061da546Spatrick         if (static_cast<size_t>(m_first_visible_line) > num_lines)
6273061da546Spatrick           m_first_visible_line = num_lines - num_visible_lines;
6274061da546Spatrick       }
6275061da546Spatrick       break;
6276061da546Spatrick 
6277061da546Spatrick     default:
6278061da546Spatrick       done = true;
6279061da546Spatrick       break;
6280061da546Spatrick     }
6281061da546Spatrick   }
6282061da546Spatrick   if (done)
6283061da546Spatrick     window.GetParent()->RemoveSubWindow(&window);
6284061da546Spatrick   return eKeyHandled;
6285061da546Spatrick }
6286061da546Spatrick 
6287061da546Spatrick class ApplicationDelegate : public WindowDelegate, public MenuDelegate {
6288061da546Spatrick public:
6289061da546Spatrick   enum {
6290061da546Spatrick     eMenuID_LLDB = 1,
6291061da546Spatrick     eMenuID_LLDBAbout,
6292061da546Spatrick     eMenuID_LLDBExit,
6293061da546Spatrick 
6294061da546Spatrick     eMenuID_Target,
6295061da546Spatrick     eMenuID_TargetCreate,
6296061da546Spatrick     eMenuID_TargetDelete,
6297061da546Spatrick 
6298061da546Spatrick     eMenuID_Process,
6299061da546Spatrick     eMenuID_ProcessAttach,
6300be691f3bSpatrick     eMenuID_ProcessDetachResume,
6301be691f3bSpatrick     eMenuID_ProcessDetachSuspended,
6302061da546Spatrick     eMenuID_ProcessLaunch,
6303061da546Spatrick     eMenuID_ProcessContinue,
6304061da546Spatrick     eMenuID_ProcessHalt,
6305061da546Spatrick     eMenuID_ProcessKill,
6306061da546Spatrick 
6307061da546Spatrick     eMenuID_Thread,
6308061da546Spatrick     eMenuID_ThreadStepIn,
6309061da546Spatrick     eMenuID_ThreadStepOver,
6310061da546Spatrick     eMenuID_ThreadStepOut,
6311061da546Spatrick 
6312061da546Spatrick     eMenuID_View,
6313061da546Spatrick     eMenuID_ViewBacktrace,
6314061da546Spatrick     eMenuID_ViewRegisters,
6315061da546Spatrick     eMenuID_ViewSource,
6316061da546Spatrick     eMenuID_ViewVariables,
6317*f6aab3d8Srobert     eMenuID_ViewBreakpoints,
6318061da546Spatrick 
6319061da546Spatrick     eMenuID_Help,
6320061da546Spatrick     eMenuID_HelpGUIHelp
6321061da546Spatrick   };
6322061da546Spatrick 
ApplicationDelegate(Application & app,Debugger & debugger)6323061da546Spatrick   ApplicationDelegate(Application &app, Debugger &debugger)
6324061da546Spatrick       : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {}
6325061da546Spatrick 
6326061da546Spatrick   ~ApplicationDelegate() override = default;
6327061da546Spatrick 
WindowDelegateDraw(Window & window,bool force)6328061da546Spatrick   bool WindowDelegateDraw(Window &window, bool force) override {
6329061da546Spatrick     return false; // Drawing not handled, let standard window drawing happen
6330061da546Spatrick   }
6331061da546Spatrick 
WindowDelegateHandleChar(Window & window,int key)6332061da546Spatrick   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
6333061da546Spatrick     switch (key) {
6334061da546Spatrick     case '\t':
6335061da546Spatrick       window.SelectNextWindowAsActive();
6336061da546Spatrick       return eKeyHandled;
6337061da546Spatrick 
6338be691f3bSpatrick     case KEY_SHIFT_TAB:
6339be691f3bSpatrick       window.SelectPreviousWindowAsActive();
6340be691f3bSpatrick       return eKeyHandled;
6341be691f3bSpatrick 
6342061da546Spatrick     case 'h':
6343061da546Spatrick       window.CreateHelpSubwindow();
6344061da546Spatrick       return eKeyHandled;
6345061da546Spatrick 
6346061da546Spatrick     case KEY_ESCAPE:
6347061da546Spatrick       return eQuitApplication;
6348061da546Spatrick 
6349061da546Spatrick     default:
6350061da546Spatrick       break;
6351061da546Spatrick     }
6352061da546Spatrick     return eKeyNotHandled;
6353061da546Spatrick   }
6354061da546Spatrick 
WindowDelegateGetHelpText()6355061da546Spatrick   const char *WindowDelegateGetHelpText() override {
6356061da546Spatrick     return "Welcome to the LLDB curses GUI.\n\n"
6357061da546Spatrick            "Press the TAB key to change the selected view.\n"
6358061da546Spatrick            "Each view has its own keyboard shortcuts, press 'h' to open a "
6359061da546Spatrick            "dialog to display them.\n\n"
6360061da546Spatrick            "Common key bindings for all views:";
6361061da546Spatrick   }
6362061da546Spatrick 
WindowDelegateGetKeyHelp()6363061da546Spatrick   KeyHelp *WindowDelegateGetKeyHelp() override {
6364061da546Spatrick     static curses::KeyHelp g_source_view_key_help[] = {
6365061da546Spatrick         {'\t', "Select next view"},
6366be691f3bSpatrick         {KEY_BTAB, "Select previous view"},
6367061da546Spatrick         {'h', "Show help dialog with view specific key bindings"},
6368061da546Spatrick         {',', "Page up"},
6369061da546Spatrick         {'.', "Page down"},
6370061da546Spatrick         {KEY_UP, "Select previous"},
6371061da546Spatrick         {KEY_DOWN, "Select next"},
6372061da546Spatrick         {KEY_LEFT, "Unexpand or select parent"},
6373061da546Spatrick         {KEY_RIGHT, "Expand"},
6374061da546Spatrick         {KEY_PPAGE, "Page up"},
6375061da546Spatrick         {KEY_NPAGE, "Page down"},
6376061da546Spatrick         {'\0', nullptr}};
6377061da546Spatrick     return g_source_view_key_help;
6378061da546Spatrick   }
6379061da546Spatrick 
MenuDelegateAction(Menu & menu)6380061da546Spatrick   MenuActionResult MenuDelegateAction(Menu &menu) override {
6381061da546Spatrick     switch (menu.GetIdentifier()) {
6382*f6aab3d8Srobert     case eMenuID_TargetCreate: {
6383*f6aab3d8Srobert       WindowSP main_window_sp = m_app.GetMainWindow();
6384*f6aab3d8Srobert       FormDelegateSP form_delegate_sp =
6385*f6aab3d8Srobert           FormDelegateSP(new TargetCreateFormDelegate(m_debugger));
6386*f6aab3d8Srobert       Rect bounds = main_window_sp->GetCenteredRect(80, 19);
6387*f6aab3d8Srobert       WindowSP form_window_sp = main_window_sp->CreateSubWindow(
6388*f6aab3d8Srobert           form_delegate_sp->GetName().c_str(), bounds, true);
6389*f6aab3d8Srobert       WindowDelegateSP window_delegate_sp =
6390*f6aab3d8Srobert           WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
6391*f6aab3d8Srobert       form_window_sp->SetDelegate(window_delegate_sp);
6392*f6aab3d8Srobert       return MenuActionResult::Handled;
6393*f6aab3d8Srobert     }
6394061da546Spatrick     case eMenuID_ThreadStepIn: {
6395061da546Spatrick       ExecutionContext exe_ctx =
6396061da546Spatrick           m_debugger.GetCommandInterpreter().GetExecutionContext();
6397061da546Spatrick       if (exe_ctx.HasThreadScope()) {
6398061da546Spatrick         Process *process = exe_ctx.GetProcessPtr();
6399061da546Spatrick         if (process && process->IsAlive() &&
6400061da546Spatrick             StateIsStoppedState(process->GetState(), true))
6401061da546Spatrick           exe_ctx.GetThreadRef().StepIn(true);
6402061da546Spatrick       }
6403061da546Spatrick     }
6404061da546Spatrick       return MenuActionResult::Handled;
6405061da546Spatrick 
6406061da546Spatrick     case eMenuID_ThreadStepOut: {
6407061da546Spatrick       ExecutionContext exe_ctx =
6408061da546Spatrick           m_debugger.GetCommandInterpreter().GetExecutionContext();
6409061da546Spatrick       if (exe_ctx.HasThreadScope()) {
6410061da546Spatrick         Process *process = exe_ctx.GetProcessPtr();
6411061da546Spatrick         if (process && process->IsAlive() &&
6412*f6aab3d8Srobert             StateIsStoppedState(process->GetState(), true)) {
6413*f6aab3d8Srobert           Thread *thread = exe_ctx.GetThreadPtr();
6414*f6aab3d8Srobert           uint32_t frame_idx = thread->GetSelectedFrameIndex();
6415*f6aab3d8Srobert           exe_ctx.GetThreadRef().StepOut(frame_idx);
6416*f6aab3d8Srobert         }
6417061da546Spatrick       }
6418061da546Spatrick     }
6419061da546Spatrick       return MenuActionResult::Handled;
6420061da546Spatrick 
6421061da546Spatrick     case eMenuID_ThreadStepOver: {
6422061da546Spatrick       ExecutionContext exe_ctx =
6423061da546Spatrick           m_debugger.GetCommandInterpreter().GetExecutionContext();
6424061da546Spatrick       if (exe_ctx.HasThreadScope()) {
6425061da546Spatrick         Process *process = exe_ctx.GetProcessPtr();
6426061da546Spatrick         if (process && process->IsAlive() &&
6427061da546Spatrick             StateIsStoppedState(process->GetState(), true))
6428061da546Spatrick           exe_ctx.GetThreadRef().StepOver(true);
6429061da546Spatrick       }
6430061da546Spatrick     }
6431061da546Spatrick       return MenuActionResult::Handled;
6432061da546Spatrick 
6433be691f3bSpatrick     case eMenuID_ProcessAttach: {
6434be691f3bSpatrick       WindowSP main_window_sp = m_app.GetMainWindow();
6435be691f3bSpatrick       FormDelegateSP form_delegate_sp = FormDelegateSP(
6436be691f3bSpatrick           new ProcessAttachFormDelegate(m_debugger, main_window_sp));
6437be691f3bSpatrick       Rect bounds = main_window_sp->GetCenteredRect(80, 22);
6438be691f3bSpatrick       WindowSP form_window_sp = main_window_sp->CreateSubWindow(
6439be691f3bSpatrick           form_delegate_sp->GetName().c_str(), bounds, true);
6440be691f3bSpatrick       WindowDelegateSP window_delegate_sp =
6441be691f3bSpatrick           WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
6442be691f3bSpatrick       form_window_sp->SetDelegate(window_delegate_sp);
6443be691f3bSpatrick       return MenuActionResult::Handled;
6444be691f3bSpatrick     }
6445*f6aab3d8Srobert     case eMenuID_ProcessLaunch: {
6446*f6aab3d8Srobert       WindowSP main_window_sp = m_app.GetMainWindow();
6447*f6aab3d8Srobert       FormDelegateSP form_delegate_sp = FormDelegateSP(
6448*f6aab3d8Srobert           new ProcessLaunchFormDelegate(m_debugger, main_window_sp));
6449*f6aab3d8Srobert       Rect bounds = main_window_sp->GetCenteredRect(80, 22);
6450*f6aab3d8Srobert       WindowSP form_window_sp = main_window_sp->CreateSubWindow(
6451*f6aab3d8Srobert           form_delegate_sp->GetName().c_str(), bounds, true);
6452*f6aab3d8Srobert       WindowDelegateSP window_delegate_sp =
6453*f6aab3d8Srobert           WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
6454*f6aab3d8Srobert       form_window_sp->SetDelegate(window_delegate_sp);
6455*f6aab3d8Srobert       return MenuActionResult::Handled;
6456*f6aab3d8Srobert     }
6457be691f3bSpatrick 
6458061da546Spatrick     case eMenuID_ProcessContinue: {
6459061da546Spatrick       ExecutionContext exe_ctx =
6460061da546Spatrick           m_debugger.GetCommandInterpreter().GetExecutionContext();
6461061da546Spatrick       if (exe_ctx.HasProcessScope()) {
6462061da546Spatrick         Process *process = exe_ctx.GetProcessPtr();
6463061da546Spatrick         if (process && process->IsAlive() &&
6464061da546Spatrick             StateIsStoppedState(process->GetState(), true))
6465061da546Spatrick           process->Resume();
6466061da546Spatrick       }
6467061da546Spatrick     }
6468061da546Spatrick       return MenuActionResult::Handled;
6469061da546Spatrick 
6470061da546Spatrick     case eMenuID_ProcessKill: {
6471061da546Spatrick       ExecutionContext exe_ctx =
6472061da546Spatrick           m_debugger.GetCommandInterpreter().GetExecutionContext();
6473061da546Spatrick       if (exe_ctx.HasProcessScope()) {
6474061da546Spatrick         Process *process = exe_ctx.GetProcessPtr();
6475061da546Spatrick         if (process && process->IsAlive())
6476061da546Spatrick           process->Destroy(false);
6477061da546Spatrick       }
6478061da546Spatrick     }
6479061da546Spatrick       return MenuActionResult::Handled;
6480061da546Spatrick 
6481061da546Spatrick     case eMenuID_ProcessHalt: {
6482061da546Spatrick       ExecutionContext exe_ctx =
6483061da546Spatrick           m_debugger.GetCommandInterpreter().GetExecutionContext();
6484061da546Spatrick       if (exe_ctx.HasProcessScope()) {
6485061da546Spatrick         Process *process = exe_ctx.GetProcessPtr();
6486061da546Spatrick         if (process && process->IsAlive())
6487061da546Spatrick           process->Halt();
6488061da546Spatrick       }
6489061da546Spatrick     }
6490061da546Spatrick       return MenuActionResult::Handled;
6491061da546Spatrick 
6492be691f3bSpatrick     case eMenuID_ProcessDetachResume:
6493be691f3bSpatrick     case eMenuID_ProcessDetachSuspended: {
6494061da546Spatrick       ExecutionContext exe_ctx =
6495061da546Spatrick           m_debugger.GetCommandInterpreter().GetExecutionContext();
6496061da546Spatrick       if (exe_ctx.HasProcessScope()) {
6497061da546Spatrick         Process *process = exe_ctx.GetProcessPtr();
6498061da546Spatrick         if (process && process->IsAlive())
6499be691f3bSpatrick           process->Detach(menu.GetIdentifier() ==
6500be691f3bSpatrick                           eMenuID_ProcessDetachSuspended);
6501061da546Spatrick       }
6502061da546Spatrick     }
6503061da546Spatrick       return MenuActionResult::Handled;
6504061da546Spatrick 
6505061da546Spatrick     case eMenuID_Process: {
6506061da546Spatrick       // Populate the menu with all of the threads if the process is stopped
6507061da546Spatrick       // when the Process menu gets selected and is about to display its
6508061da546Spatrick       // submenu.
6509061da546Spatrick       Menus &submenus = menu.GetSubmenus();
6510061da546Spatrick       ExecutionContext exe_ctx =
6511061da546Spatrick           m_debugger.GetCommandInterpreter().GetExecutionContext();
6512061da546Spatrick       Process *process = exe_ctx.GetProcessPtr();
6513061da546Spatrick       if (process && process->IsAlive() &&
6514061da546Spatrick           StateIsStoppedState(process->GetState(), true)) {
6515061da546Spatrick         if (submenus.size() == 7)
6516061da546Spatrick           menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
6517061da546Spatrick         else if (submenus.size() > 8)
6518061da546Spatrick           submenus.erase(submenus.begin() + 8, submenus.end());
6519061da546Spatrick 
6520061da546Spatrick         ThreadList &threads = process->GetThreadList();
6521061da546Spatrick         std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
6522061da546Spatrick         size_t num_threads = threads.GetSize();
6523061da546Spatrick         for (size_t i = 0; i < num_threads; ++i) {
6524061da546Spatrick           ThreadSP thread_sp = threads.GetThreadAtIndex(i);
6525061da546Spatrick           char menu_char = '\0';
6526061da546Spatrick           if (i < 9)
6527061da546Spatrick             menu_char = '1' + i;
6528061da546Spatrick           StreamString thread_menu_title;
6529061da546Spatrick           thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID());
6530061da546Spatrick           const char *thread_name = thread_sp->GetName();
6531061da546Spatrick           if (thread_name && thread_name[0])
6532061da546Spatrick             thread_menu_title.Printf(" %s", thread_name);
6533061da546Spatrick           else {
6534061da546Spatrick             const char *queue_name = thread_sp->GetQueueName();
6535061da546Spatrick             if (queue_name && queue_name[0])
6536061da546Spatrick               thread_menu_title.Printf(" %s", queue_name);
6537061da546Spatrick           }
6538061da546Spatrick           menu.AddSubmenu(
6539061da546Spatrick               MenuSP(new Menu(thread_menu_title.GetString().str().c_str(),
6540061da546Spatrick                               nullptr, menu_char, thread_sp->GetID())));
6541061da546Spatrick         }
6542061da546Spatrick       } else if (submenus.size() > 7) {
6543061da546Spatrick         // Remove the separator and any other thread submenu items that were
6544061da546Spatrick         // previously added
6545061da546Spatrick         submenus.erase(submenus.begin() + 7, submenus.end());
6546061da546Spatrick       }
6547*f6aab3d8Srobert       // Since we are adding and removing items we need to recalculate the
6548*f6aab3d8Srobert       // name lengths
6549061da546Spatrick       menu.RecalculateNameLengths();
6550061da546Spatrick     }
6551061da546Spatrick       return MenuActionResult::Handled;
6552061da546Spatrick 
6553061da546Spatrick     case eMenuID_ViewVariables: {
6554061da546Spatrick       WindowSP main_window_sp = m_app.GetMainWindow();
6555061da546Spatrick       WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
6556061da546Spatrick       WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
6557061da546Spatrick       WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
6558061da546Spatrick       const Rect source_bounds = source_window_sp->GetBounds();
6559061da546Spatrick 
6560061da546Spatrick       if (variables_window_sp) {
6561061da546Spatrick         const Rect variables_bounds = variables_window_sp->GetBounds();
6562061da546Spatrick 
6563061da546Spatrick         main_window_sp->RemoveSubWindow(variables_window_sp.get());
6564061da546Spatrick 
6565061da546Spatrick         if (registers_window_sp) {
6566061da546Spatrick           // We have a registers window, so give all the area back to the
6567061da546Spatrick           // registers window
6568061da546Spatrick           Rect registers_bounds = variables_bounds;
6569061da546Spatrick           registers_bounds.size.width = source_bounds.size.width;
6570061da546Spatrick           registers_window_sp->SetBounds(registers_bounds);
6571061da546Spatrick         } else {
6572061da546Spatrick           // We have no registers window showing so give the bottom area back
6573061da546Spatrick           // to the source view
6574061da546Spatrick           source_window_sp->Resize(source_bounds.size.width,
6575061da546Spatrick                                    source_bounds.size.height +
6576061da546Spatrick                                        variables_bounds.size.height);
6577061da546Spatrick         }
6578061da546Spatrick       } else {
6579061da546Spatrick         Rect new_variables_rect;
6580061da546Spatrick         if (registers_window_sp) {
6581061da546Spatrick           // We have a registers window so split the area of the registers
6582061da546Spatrick           // window into two columns where the left hand side will be the
6583061da546Spatrick           // variables and the right hand side will be the registers
6584061da546Spatrick           const Rect variables_bounds = registers_window_sp->GetBounds();
6585061da546Spatrick           Rect new_registers_rect;
6586061da546Spatrick           variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect,
6587061da546Spatrick                                                    new_registers_rect);
6588061da546Spatrick           registers_window_sp->SetBounds(new_registers_rect);
6589061da546Spatrick         } else {
6590be691f3bSpatrick           // No registers window, grab the bottom part of the source window
6591061da546Spatrick           Rect new_source_rect;
6592061da546Spatrick           source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
6593061da546Spatrick                                                   new_variables_rect);
6594061da546Spatrick           source_window_sp->SetBounds(new_source_rect);
6595061da546Spatrick         }
6596061da546Spatrick         WindowSP new_window_sp = main_window_sp->CreateSubWindow(
6597061da546Spatrick             "Variables", new_variables_rect, false);
6598061da546Spatrick         new_window_sp->SetDelegate(
6599061da546Spatrick             WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
6600061da546Spatrick       }
6601061da546Spatrick       touchwin(stdscr);
6602061da546Spatrick     }
6603061da546Spatrick       return MenuActionResult::Handled;
6604061da546Spatrick 
6605061da546Spatrick     case eMenuID_ViewRegisters: {
6606061da546Spatrick       WindowSP main_window_sp = m_app.GetMainWindow();
6607061da546Spatrick       WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
6608061da546Spatrick       WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
6609061da546Spatrick       WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
6610061da546Spatrick       const Rect source_bounds = source_window_sp->GetBounds();
6611061da546Spatrick 
6612061da546Spatrick       if (registers_window_sp) {
6613061da546Spatrick         if (variables_window_sp) {
6614061da546Spatrick           const Rect variables_bounds = variables_window_sp->GetBounds();
6615061da546Spatrick 
6616061da546Spatrick           // We have a variables window, so give all the area back to the
6617061da546Spatrick           // variables window
6618061da546Spatrick           variables_window_sp->Resize(variables_bounds.size.width +
6619061da546Spatrick                                           registers_window_sp->GetWidth(),
6620061da546Spatrick                                       variables_bounds.size.height);
6621061da546Spatrick         } else {
6622061da546Spatrick           // We have no variables window showing so give the bottom area back
6623061da546Spatrick           // to the source view
6624061da546Spatrick           source_window_sp->Resize(source_bounds.size.width,
6625061da546Spatrick                                    source_bounds.size.height +
6626061da546Spatrick                                        registers_window_sp->GetHeight());
6627061da546Spatrick         }
6628061da546Spatrick         main_window_sp->RemoveSubWindow(registers_window_sp.get());
6629061da546Spatrick       } else {
6630061da546Spatrick         Rect new_regs_rect;
6631061da546Spatrick         if (variables_window_sp) {
6632061da546Spatrick           // We have a variables window, split it into two columns where the
6633061da546Spatrick           // left hand side will be the variables and the right hand side will
6634061da546Spatrick           // be the registers
6635061da546Spatrick           const Rect variables_bounds = variables_window_sp->GetBounds();
6636061da546Spatrick           Rect new_vars_rect;
6637061da546Spatrick           variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect,
6638061da546Spatrick                                                    new_regs_rect);
6639061da546Spatrick           variables_window_sp->SetBounds(new_vars_rect);
6640061da546Spatrick         } else {
6641be691f3bSpatrick           // No variables window, grab the bottom part of the source window
6642061da546Spatrick           Rect new_source_rect;
6643061da546Spatrick           source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
6644061da546Spatrick                                                   new_regs_rect);
6645061da546Spatrick           source_window_sp->SetBounds(new_source_rect);
6646061da546Spatrick         }
6647061da546Spatrick         WindowSP new_window_sp =
6648061da546Spatrick             main_window_sp->CreateSubWindow("Registers", new_regs_rect, false);
6649061da546Spatrick         new_window_sp->SetDelegate(
6650061da546Spatrick             WindowDelegateSP(new RegistersWindowDelegate(m_debugger)));
6651061da546Spatrick       }
6652061da546Spatrick       touchwin(stdscr);
6653061da546Spatrick     }
6654061da546Spatrick       return MenuActionResult::Handled;
6655061da546Spatrick 
6656*f6aab3d8Srobert     case eMenuID_ViewBreakpoints: {
6657*f6aab3d8Srobert       WindowSP main_window_sp = m_app.GetMainWindow();
6658*f6aab3d8Srobert       WindowSP threads_window_sp = main_window_sp->FindSubWindow("Threads");
6659*f6aab3d8Srobert       WindowSP breakpoints_window_sp =
6660*f6aab3d8Srobert           main_window_sp->FindSubWindow("Breakpoints");
6661*f6aab3d8Srobert       const Rect threads_bounds = threads_window_sp->GetBounds();
6662*f6aab3d8Srobert 
6663*f6aab3d8Srobert       // If a breakpoints window already exists, remove it and give the area
6664*f6aab3d8Srobert       // it used to occupy to the threads window. If it doesn't exist, split
6665*f6aab3d8Srobert       // the threads window horizontally into two windows where the top window
6666*f6aab3d8Srobert       // is the threads window and the bottom window is a newly added
6667*f6aab3d8Srobert       // breakpoints window.
6668*f6aab3d8Srobert       if (breakpoints_window_sp) {
6669*f6aab3d8Srobert         threads_window_sp->Resize(threads_bounds.size.width,
6670*f6aab3d8Srobert                                   threads_bounds.size.height +
6671*f6aab3d8Srobert                                       breakpoints_window_sp->GetHeight());
6672*f6aab3d8Srobert         main_window_sp->RemoveSubWindow(breakpoints_window_sp.get());
6673*f6aab3d8Srobert       } else {
6674*f6aab3d8Srobert         Rect new_threads_bounds, breakpoints_bounds;
6675*f6aab3d8Srobert         threads_bounds.HorizontalSplitPercentage(0.70, new_threads_bounds,
6676*f6aab3d8Srobert                                                  breakpoints_bounds);
6677*f6aab3d8Srobert         threads_window_sp->SetBounds(new_threads_bounds);
6678*f6aab3d8Srobert         breakpoints_window_sp = main_window_sp->CreateSubWindow(
6679*f6aab3d8Srobert             "Breakpoints", breakpoints_bounds, false);
6680*f6aab3d8Srobert         TreeDelegateSP breakpoints_delegate_sp(
6681*f6aab3d8Srobert             new BreakpointsTreeDelegate(m_debugger));
6682*f6aab3d8Srobert         breakpoints_window_sp->SetDelegate(WindowDelegateSP(
6683*f6aab3d8Srobert             new TreeWindowDelegate(m_debugger, breakpoints_delegate_sp)));
6684*f6aab3d8Srobert       }
6685*f6aab3d8Srobert       touchwin(stdscr);
6686*f6aab3d8Srobert       return MenuActionResult::Handled;
6687*f6aab3d8Srobert     }
6688*f6aab3d8Srobert 
6689061da546Spatrick     case eMenuID_HelpGUIHelp:
6690061da546Spatrick       m_app.GetMainWindow()->CreateHelpSubwindow();
6691061da546Spatrick       return MenuActionResult::Handled;
6692061da546Spatrick 
6693061da546Spatrick     default:
6694061da546Spatrick       break;
6695061da546Spatrick     }
6696061da546Spatrick 
6697061da546Spatrick     return MenuActionResult::NotHandled;
6698061da546Spatrick   }
6699061da546Spatrick 
6700061da546Spatrick protected:
6701061da546Spatrick   Application &m_app;
6702061da546Spatrick   Debugger &m_debugger;
6703061da546Spatrick };
6704061da546Spatrick 
6705061da546Spatrick class StatusBarWindowDelegate : public WindowDelegate {
6706061da546Spatrick public:
StatusBarWindowDelegate(Debugger & debugger)6707061da546Spatrick   StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) {
6708061da546Spatrick     FormatEntity::Parse("Thread: ${thread.id%tid}", m_format);
6709061da546Spatrick   }
6710061da546Spatrick 
6711061da546Spatrick   ~StatusBarWindowDelegate() override = default;
6712061da546Spatrick 
WindowDelegateDraw(Window & window,bool force)6713061da546Spatrick   bool WindowDelegateDraw(Window &window, bool force) override {
6714061da546Spatrick     ExecutionContext exe_ctx =
6715061da546Spatrick         m_debugger.GetCommandInterpreter().GetExecutionContext();
6716061da546Spatrick     Process *process = exe_ctx.GetProcessPtr();
6717061da546Spatrick     Thread *thread = exe_ctx.GetThreadPtr();
6718061da546Spatrick     StackFrame *frame = exe_ctx.GetFramePtr();
6719061da546Spatrick     window.Erase();
6720be691f3bSpatrick     window.SetBackground(BlackOnWhite);
6721061da546Spatrick     window.MoveCursor(0, 0);
6722061da546Spatrick     if (process) {
6723061da546Spatrick       const StateType state = process->GetState();
6724061da546Spatrick       window.Printf("Process: %5" PRIu64 " %10s", process->GetID(),
6725061da546Spatrick                     StateAsCString(state));
6726061da546Spatrick 
6727061da546Spatrick       if (StateIsStoppedState(state, true)) {
6728061da546Spatrick         StreamString strm;
6729061da546Spatrick         if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx,
6730061da546Spatrick                                            nullptr, nullptr, false, false)) {
6731061da546Spatrick           window.MoveCursor(40, 0);
6732be691f3bSpatrick           window.PutCStringTruncated(1, strm.GetString().str().c_str());
6733061da546Spatrick         }
6734061da546Spatrick 
6735061da546Spatrick         window.MoveCursor(60, 0);
6736061da546Spatrick         if (frame)
6737061da546Spatrick           window.Printf("Frame: %3u  PC = 0x%16.16" PRIx64,
6738061da546Spatrick                         frame->GetFrameIndex(),
6739061da546Spatrick                         frame->GetFrameCodeAddress().GetOpcodeLoadAddress(
6740061da546Spatrick                             exe_ctx.GetTargetPtr()));
6741061da546Spatrick       } else if (state == eStateExited) {
6742061da546Spatrick         const char *exit_desc = process->GetExitDescription();
6743061da546Spatrick         const int exit_status = process->GetExitStatus();
6744061da546Spatrick         if (exit_desc && exit_desc[0])
6745061da546Spatrick           window.Printf(" with status = %i (%s)", exit_status, exit_desc);
6746061da546Spatrick         else
6747061da546Spatrick           window.Printf(" with status = %i", exit_status);
6748061da546Spatrick       }
6749061da546Spatrick     }
6750061da546Spatrick     return true;
6751061da546Spatrick   }
6752061da546Spatrick 
6753061da546Spatrick protected:
6754061da546Spatrick   Debugger &m_debugger;
6755061da546Spatrick   FormatEntity::Entry m_format;
6756061da546Spatrick };
6757061da546Spatrick 
6758061da546Spatrick class SourceFileWindowDelegate : public WindowDelegate {
6759061da546Spatrick public:
SourceFileWindowDelegate(Debugger & debugger)6760061da546Spatrick   SourceFileWindowDelegate(Debugger &debugger)
6761061da546Spatrick       : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(),
6762*f6aab3d8Srobert         m_disassembly_sp(), m_disassembly_range(), m_title() {}
6763061da546Spatrick 
6764061da546Spatrick   ~SourceFileWindowDelegate() override = default;
6765061da546Spatrick 
Update(const SymbolContext & sc)6766061da546Spatrick   void Update(const SymbolContext &sc) { m_sc = sc; }
6767061da546Spatrick 
NumVisibleLines() const6768061da546Spatrick   uint32_t NumVisibleLines() const { return m_max_y - m_min_y; }
6769061da546Spatrick 
WindowDelegateGetHelpText()6770061da546Spatrick   const char *WindowDelegateGetHelpText() override {
6771061da546Spatrick     return "Source/Disassembly window keyboard shortcuts:";
6772061da546Spatrick   }
6773061da546Spatrick 
WindowDelegateGetKeyHelp()6774061da546Spatrick   KeyHelp *WindowDelegateGetKeyHelp() override {
6775061da546Spatrick     static curses::KeyHelp g_source_view_key_help[] = {
6776061da546Spatrick         {KEY_RETURN, "Run to selected line with one shot breakpoint"},
6777061da546Spatrick         {KEY_UP, "Select previous source line"},
6778061da546Spatrick         {KEY_DOWN, "Select next source line"},
6779be691f3bSpatrick         {KEY_LEFT, "Scroll to the left"},
6780be691f3bSpatrick         {KEY_RIGHT, "Scroll to the right"},
6781061da546Spatrick         {KEY_PPAGE, "Page up"},
6782061da546Spatrick         {KEY_NPAGE, "Page down"},
6783061da546Spatrick         {'b', "Set breakpoint on selected source/disassembly line"},
6784061da546Spatrick         {'c', "Continue process"},
6785061da546Spatrick         {'D', "Detach with process suspended"},
6786061da546Spatrick         {'h', "Show help dialog"},
6787061da546Spatrick         {'n', "Step over (source line)"},
6788061da546Spatrick         {'N', "Step over (single instruction)"},
6789be691f3bSpatrick         {'f', "Step out (finish)"},
6790061da546Spatrick         {'s', "Step in (source line)"},
6791061da546Spatrick         {'S', "Step in (single instruction)"},
6792be691f3bSpatrick         {'u', "Frame up"},
6793be691f3bSpatrick         {'d', "Frame down"},
6794061da546Spatrick         {',', "Page up"},
6795061da546Spatrick         {'.', "Page down"},
6796061da546Spatrick         {'\0', nullptr}};
6797061da546Spatrick     return g_source_view_key_help;
6798061da546Spatrick   }
6799061da546Spatrick 
WindowDelegateDraw(Window & window,bool force)6800061da546Spatrick   bool WindowDelegateDraw(Window &window, bool force) override {
6801061da546Spatrick     ExecutionContext exe_ctx =
6802061da546Spatrick         m_debugger.GetCommandInterpreter().GetExecutionContext();
6803061da546Spatrick     Process *process = exe_ctx.GetProcessPtr();
6804061da546Spatrick     Thread *thread = nullptr;
6805061da546Spatrick 
6806061da546Spatrick     bool update_location = false;
6807061da546Spatrick     if (process) {
6808061da546Spatrick       StateType state = process->GetState();
6809061da546Spatrick       if (StateIsStoppedState(state, true)) {
6810061da546Spatrick         // We are stopped, so it is ok to
6811061da546Spatrick         update_location = true;
6812061da546Spatrick       }
6813061da546Spatrick     }
6814061da546Spatrick 
6815061da546Spatrick     m_min_x = 1;
6816061da546Spatrick     m_min_y = 2;
6817061da546Spatrick     m_max_x = window.GetMaxX() - 1;
6818061da546Spatrick     m_max_y = window.GetMaxY() - 1;
6819061da546Spatrick 
6820061da546Spatrick     const uint32_t num_visible_lines = NumVisibleLines();
6821061da546Spatrick     StackFrameSP frame_sp;
6822061da546Spatrick     bool set_selected_line_to_pc = false;
6823061da546Spatrick 
6824061da546Spatrick     if (update_location) {
6825*f6aab3d8Srobert       const bool process_alive = process->IsAlive();
6826061da546Spatrick       bool thread_changed = false;
6827061da546Spatrick       if (process_alive) {
6828061da546Spatrick         thread = exe_ctx.GetThreadPtr();
6829061da546Spatrick         if (thread) {
6830061da546Spatrick           frame_sp = thread->GetSelectedFrame();
6831061da546Spatrick           auto tid = thread->GetID();
6832061da546Spatrick           thread_changed = tid != m_tid;
6833061da546Spatrick           m_tid = tid;
6834061da546Spatrick         } else {
6835061da546Spatrick           if (m_tid != LLDB_INVALID_THREAD_ID) {
6836061da546Spatrick             thread_changed = true;
6837061da546Spatrick             m_tid = LLDB_INVALID_THREAD_ID;
6838061da546Spatrick           }
6839061da546Spatrick         }
6840061da546Spatrick       }
6841061da546Spatrick       const uint32_t stop_id = process ? process->GetStopID() : 0;
6842061da546Spatrick       const bool stop_id_changed = stop_id != m_stop_id;
6843061da546Spatrick       bool frame_changed = false;
6844061da546Spatrick       m_stop_id = stop_id;
6845061da546Spatrick       m_title.Clear();
6846061da546Spatrick       if (frame_sp) {
6847061da546Spatrick         m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything);
6848061da546Spatrick         if (m_sc.module_sp) {
6849061da546Spatrick           m_title.Printf(
6850061da546Spatrick               "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString());
6851061da546Spatrick           ConstString func_name = m_sc.GetFunctionName();
6852061da546Spatrick           if (func_name)
6853061da546Spatrick             m_title.Printf("`%s", func_name.GetCString());
6854061da546Spatrick         }
6855061da546Spatrick         const uint32_t frame_idx = frame_sp->GetFrameIndex();
6856061da546Spatrick         frame_changed = frame_idx != m_frame_idx;
6857061da546Spatrick         m_frame_idx = frame_idx;
6858061da546Spatrick       } else {
6859061da546Spatrick         m_sc.Clear(true);
6860061da546Spatrick         frame_changed = m_frame_idx != UINT32_MAX;
6861061da546Spatrick         m_frame_idx = UINT32_MAX;
6862061da546Spatrick       }
6863061da546Spatrick 
6864061da546Spatrick       const bool context_changed =
6865061da546Spatrick           thread_changed || frame_changed || stop_id_changed;
6866061da546Spatrick 
6867061da546Spatrick       if (process_alive) {
6868061da546Spatrick         if (m_sc.line_entry.IsValid()) {
6869061da546Spatrick           m_pc_line = m_sc.line_entry.line;
6870061da546Spatrick           if (m_pc_line != UINT32_MAX)
6871061da546Spatrick             --m_pc_line; // Convert to zero based line number...
6872061da546Spatrick           // Update the selected line if the stop ID changed...
6873061da546Spatrick           if (context_changed)
6874061da546Spatrick             m_selected_line = m_pc_line;
6875061da546Spatrick 
6876061da546Spatrick           if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) {
6877*f6aab3d8Srobert             // Same file, nothing to do, we should either have the lines or
6878*f6aab3d8Srobert             // not (source file missing)
6879061da546Spatrick             if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) {
6880061da546Spatrick               if (m_selected_line >= m_first_visible_line + num_visible_lines)
6881061da546Spatrick                 m_first_visible_line = m_selected_line - 10;
6882061da546Spatrick             } else {
6883061da546Spatrick               if (m_selected_line > 10)
6884061da546Spatrick                 m_first_visible_line = m_selected_line - 10;
6885061da546Spatrick               else
6886061da546Spatrick                 m_first_visible_line = 0;
6887061da546Spatrick             }
6888061da546Spatrick           } else {
6889061da546Spatrick             // File changed, set selected line to the line with the PC
6890061da546Spatrick             m_selected_line = m_pc_line;
6891061da546Spatrick             m_file_sp =
6892061da546Spatrick                 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file);
6893061da546Spatrick             if (m_file_sp) {
6894061da546Spatrick               const size_t num_lines = m_file_sp->GetNumLines();
6895061da546Spatrick               m_line_width = 1;
6896061da546Spatrick               for (size_t n = num_lines; n >= 10; n = n / 10)
6897061da546Spatrick                 ++m_line_width;
6898061da546Spatrick 
6899061da546Spatrick               if (num_lines < num_visible_lines ||
6900061da546Spatrick                   m_selected_line < num_visible_lines)
6901061da546Spatrick                 m_first_visible_line = 0;
6902061da546Spatrick               else
6903061da546Spatrick                 m_first_visible_line = m_selected_line - 10;
6904061da546Spatrick             }
6905061da546Spatrick           }
6906061da546Spatrick         } else {
6907061da546Spatrick           m_file_sp.reset();
6908061da546Spatrick         }
6909061da546Spatrick 
6910061da546Spatrick         if (!m_file_sp || m_file_sp->GetNumLines() == 0) {
6911061da546Spatrick           // Show disassembly
6912061da546Spatrick           bool prefer_file_cache = false;
6913061da546Spatrick           if (m_sc.function) {
6914061da546Spatrick             if (m_disassembly_scope != m_sc.function) {
6915061da546Spatrick               m_disassembly_scope = m_sc.function;
6916061da546Spatrick               m_disassembly_sp = m_sc.function->GetInstructions(
6917be691f3bSpatrick                   exe_ctx, nullptr, !prefer_file_cache);
6918061da546Spatrick               if (m_disassembly_sp) {
6919061da546Spatrick                 set_selected_line_to_pc = true;
6920061da546Spatrick                 m_disassembly_range = m_sc.function->GetAddressRange();
6921061da546Spatrick               } else {
6922061da546Spatrick                 m_disassembly_range.Clear();
6923061da546Spatrick               }
6924061da546Spatrick             } else {
6925061da546Spatrick               set_selected_line_to_pc = context_changed;
6926061da546Spatrick             }
6927061da546Spatrick           } else if (m_sc.symbol) {
6928061da546Spatrick             if (m_disassembly_scope != m_sc.symbol) {
6929061da546Spatrick               m_disassembly_scope = m_sc.symbol;
6930061da546Spatrick               m_disassembly_sp = m_sc.symbol->GetInstructions(
6931061da546Spatrick                   exe_ctx, nullptr, prefer_file_cache);
6932061da546Spatrick               if (m_disassembly_sp) {
6933061da546Spatrick                 set_selected_line_to_pc = true;
6934061da546Spatrick                 m_disassembly_range.GetBaseAddress() =
6935061da546Spatrick                     m_sc.symbol->GetAddress();
6936061da546Spatrick                 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize());
6937061da546Spatrick               } else {
6938061da546Spatrick                 m_disassembly_range.Clear();
6939061da546Spatrick               }
6940061da546Spatrick             } else {
6941061da546Spatrick               set_selected_line_to_pc = context_changed;
6942061da546Spatrick             }
6943061da546Spatrick           }
6944061da546Spatrick         }
6945061da546Spatrick       } else {
6946061da546Spatrick         m_pc_line = UINT32_MAX;
6947061da546Spatrick       }
6948061da546Spatrick     }
6949061da546Spatrick 
6950061da546Spatrick     const int window_width = window.GetWidth();
6951061da546Spatrick     window.Erase();
6952061da546Spatrick     window.DrawTitleBox("Sources");
6953061da546Spatrick     if (!m_title.GetString().empty()) {
6954061da546Spatrick       window.AttributeOn(A_REVERSE);
6955061da546Spatrick       window.MoveCursor(1, 1);
6956061da546Spatrick       window.PutChar(' ');
6957be691f3bSpatrick       window.PutCStringTruncated(1, m_title.GetString().str().c_str());
6958061da546Spatrick       int x = window.GetCursorX();
6959061da546Spatrick       if (x < window_width - 1) {
6960061da546Spatrick         window.Printf("%*s", window_width - x - 1, "");
6961061da546Spatrick       }
6962061da546Spatrick       window.AttributeOff(A_REVERSE);
6963061da546Spatrick     }
6964061da546Spatrick 
6965061da546Spatrick     Target *target = exe_ctx.GetTargetPtr();
6966061da546Spatrick     const size_t num_source_lines = GetNumSourceLines();
6967061da546Spatrick     if (num_source_lines > 0) {
6968061da546Spatrick       // Display source
6969061da546Spatrick       BreakpointLines bp_lines;
6970061da546Spatrick       if (target) {
6971061da546Spatrick         BreakpointList &bp_list = target->GetBreakpointList();
6972061da546Spatrick         const size_t num_bps = bp_list.GetSize();
6973061da546Spatrick         for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
6974061da546Spatrick           BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
6975061da546Spatrick           const size_t num_bps_locs = bp_sp->GetNumLocations();
6976061da546Spatrick           for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
6977061da546Spatrick             BreakpointLocationSP bp_loc_sp =
6978061da546Spatrick                 bp_sp->GetLocationAtIndex(bp_loc_idx);
6979061da546Spatrick             LineEntry bp_loc_line_entry;
6980061da546Spatrick             if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
6981061da546Spatrick                     bp_loc_line_entry)) {
6982061da546Spatrick               if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) {
6983061da546Spatrick                 bp_lines.insert(bp_loc_line_entry.line);
6984061da546Spatrick               }
6985061da546Spatrick             }
6986061da546Spatrick           }
6987061da546Spatrick         }
6988061da546Spatrick       }
6989061da546Spatrick 
6990061da546Spatrick       for (size_t i = 0; i < num_visible_lines; ++i) {
6991061da546Spatrick         const uint32_t curr_line = m_first_visible_line + i;
6992061da546Spatrick         if (curr_line < num_source_lines) {
6993061da546Spatrick           const int line_y = m_min_y + i;
6994061da546Spatrick           window.MoveCursor(1, line_y);
6995061da546Spatrick           const bool is_pc_line = curr_line == m_pc_line;
6996061da546Spatrick           const bool line_is_selected = m_selected_line == curr_line;
6997*f6aab3d8Srobert           // Highlight the line as the PC line first (done by passing
6998*f6aab3d8Srobert           // argument to OutputColoredStringTruncated()), then if the selected
6999*f6aab3d8Srobert           // line isn't the same as the PC line, highlight it differently.
7000061da546Spatrick           attr_t highlight_attr = 0;
7001061da546Spatrick           attr_t bp_attr = 0;
7002*f6aab3d8Srobert           if (line_is_selected && !is_pc_line)
7003*f6aab3d8Srobert             highlight_attr = A_REVERSE;
7004061da546Spatrick 
7005061da546Spatrick           if (bp_lines.find(curr_line + 1) != bp_lines.end())
7006be691f3bSpatrick             bp_attr = COLOR_PAIR(BlackOnWhite);
7007061da546Spatrick 
7008061da546Spatrick           if (bp_attr)
7009061da546Spatrick             window.AttributeOn(bp_attr);
7010061da546Spatrick 
7011061da546Spatrick           window.Printf(" %*u ", m_line_width, curr_line + 1);
7012061da546Spatrick 
7013061da546Spatrick           if (bp_attr)
7014061da546Spatrick             window.AttributeOff(bp_attr);
7015061da546Spatrick 
7016061da546Spatrick           window.PutChar(ACS_VLINE);
7017061da546Spatrick           // Mark the line with the PC with a diamond
7018061da546Spatrick           if (is_pc_line)
7019061da546Spatrick             window.PutChar(ACS_DIAMOND);
7020061da546Spatrick           else
7021061da546Spatrick             window.PutChar(' ');
7022061da546Spatrick 
7023061da546Spatrick           if (highlight_attr)
7024061da546Spatrick             window.AttributeOn(highlight_attr);
7025be691f3bSpatrick 
7026be691f3bSpatrick           StreamString lineStream;
7027*f6aab3d8Srobert 
7028*f6aab3d8Srobert           std::optional<size_t> column;
7029*f6aab3d8Srobert           if (is_pc_line && m_sc.line_entry.IsValid() && m_sc.line_entry.column)
7030*f6aab3d8Srobert             column = m_sc.line_entry.column - 1;
7031*f6aab3d8Srobert           m_file_sp->DisplaySourceLines(curr_line + 1, column, 0, 0,
7032*f6aab3d8Srobert                                         &lineStream);
7033be691f3bSpatrick           StringRef line = lineStream.GetString();
7034be691f3bSpatrick           if (line.endswith("\n"))
7035be691f3bSpatrick             line = line.drop_back();
7036be691f3bSpatrick           bool wasWritten = window.OutputColoredStringTruncated(
7037*f6aab3d8Srobert               1, line, m_first_visible_column, is_pc_line);
7038*f6aab3d8Srobert           if (!wasWritten && (line_is_selected || is_pc_line)) {
7039*f6aab3d8Srobert             // Draw an empty space to show the selected/PC line if empty,
7040be691f3bSpatrick             // or draw '<' if nothing is visible because of scrolling too much
7041be691f3bSpatrick             // to the right.
7042be691f3bSpatrick             window.PutCStringTruncated(
7043be691f3bSpatrick                 1, line.empty() && m_first_visible_column == 0 ? " " : "<");
7044be691f3bSpatrick           }
7045061da546Spatrick 
7046061da546Spatrick           if (is_pc_line && frame_sp &&
7047061da546Spatrick               frame_sp->GetConcreteFrameIndex() == 0) {
7048061da546Spatrick             StopInfoSP stop_info_sp;
7049061da546Spatrick             if (thread)
7050061da546Spatrick               stop_info_sp = thread->GetStopInfo();
7051061da546Spatrick             if (stop_info_sp) {
7052061da546Spatrick               const char *stop_description = stop_info_sp->GetDescription();
7053061da546Spatrick               if (stop_description && stop_description[0]) {
7054061da546Spatrick                 size_t stop_description_len = strlen(stop_description);
7055061da546Spatrick                 int desc_x = window_width - stop_description_len - 16;
7056be691f3bSpatrick                 if (desc_x - window.GetCursorX() > 0)
7057061da546Spatrick                   window.Printf("%*s", desc_x - window.GetCursorX(), "");
7058be691f3bSpatrick                 window.MoveCursor(window_width - stop_description_len - 16,
7059be691f3bSpatrick                                   line_y);
7060be691f3bSpatrick                 const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue);
7061be691f3bSpatrick                 window.AttributeOn(stop_reason_attr);
7062be691f3bSpatrick                 window.PrintfTruncated(1, " <<< Thread %u: %s ",
7063be691f3bSpatrick                                        thread->GetIndexID(), stop_description);
7064be691f3bSpatrick                 window.AttributeOff(stop_reason_attr);
7065061da546Spatrick               }
7066061da546Spatrick             } else {
7067061da546Spatrick               window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
7068061da546Spatrick             }
7069061da546Spatrick           }
7070061da546Spatrick           if (highlight_attr)
7071061da546Spatrick             window.AttributeOff(highlight_attr);
7072061da546Spatrick         } else {
7073061da546Spatrick           break;
7074061da546Spatrick         }
7075061da546Spatrick       }
7076061da546Spatrick     } else {
7077061da546Spatrick       size_t num_disassembly_lines = GetNumDisassemblyLines();
7078061da546Spatrick       if (num_disassembly_lines > 0) {
7079061da546Spatrick         // Display disassembly
7080061da546Spatrick         BreakpointAddrs bp_file_addrs;
7081061da546Spatrick         Target *target = exe_ctx.GetTargetPtr();
7082061da546Spatrick         if (target) {
7083061da546Spatrick           BreakpointList &bp_list = target->GetBreakpointList();
7084061da546Spatrick           const size_t num_bps = bp_list.GetSize();
7085061da546Spatrick           for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7086061da546Spatrick             BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7087061da546Spatrick             const size_t num_bps_locs = bp_sp->GetNumLocations();
7088061da546Spatrick             for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs;
7089061da546Spatrick                  ++bp_loc_idx) {
7090061da546Spatrick               BreakpointLocationSP bp_loc_sp =
7091061da546Spatrick                   bp_sp->GetLocationAtIndex(bp_loc_idx);
7092061da546Spatrick               LineEntry bp_loc_line_entry;
7093061da546Spatrick               const lldb::addr_t file_addr =
7094061da546Spatrick                   bp_loc_sp->GetAddress().GetFileAddress();
7095061da546Spatrick               if (file_addr != LLDB_INVALID_ADDRESS) {
7096061da546Spatrick                 if (m_disassembly_range.ContainsFileAddress(file_addr))
7097061da546Spatrick                   bp_file_addrs.insert(file_addr);
7098061da546Spatrick               }
7099061da546Spatrick             }
7100061da546Spatrick           }
7101061da546Spatrick         }
7102061da546Spatrick 
7103061da546Spatrick         const attr_t selected_highlight_attr = A_REVERSE;
7104be691f3bSpatrick         const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue);
7105061da546Spatrick 
7106061da546Spatrick         StreamString strm;
7107061da546Spatrick 
7108061da546Spatrick         InstructionList &insts = m_disassembly_sp->GetInstructionList();
7109061da546Spatrick         Address pc_address;
7110061da546Spatrick 
7111061da546Spatrick         if (frame_sp)
7112061da546Spatrick           pc_address = frame_sp->GetFrameCodeAddress();
7113061da546Spatrick         const uint32_t pc_idx =
7114061da546Spatrick             pc_address.IsValid()
7115061da546Spatrick                 ? insts.GetIndexOfInstructionAtAddress(pc_address)
7116061da546Spatrick                 : UINT32_MAX;
7117061da546Spatrick         if (set_selected_line_to_pc) {
7118061da546Spatrick           m_selected_line = pc_idx;
7119061da546Spatrick         }
7120061da546Spatrick 
7121061da546Spatrick         const uint32_t non_visible_pc_offset = (num_visible_lines / 5);
7122061da546Spatrick         if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines)
7123061da546Spatrick           m_first_visible_line = 0;
7124061da546Spatrick 
7125061da546Spatrick         if (pc_idx < num_disassembly_lines) {
7126061da546Spatrick           if (pc_idx < static_cast<uint32_t>(m_first_visible_line) ||
7127061da546Spatrick               pc_idx >= m_first_visible_line + num_visible_lines)
7128061da546Spatrick             m_first_visible_line = pc_idx - non_visible_pc_offset;
7129061da546Spatrick         }
7130061da546Spatrick 
7131061da546Spatrick         for (size_t i = 0; i < num_visible_lines; ++i) {
7132061da546Spatrick           const uint32_t inst_idx = m_first_visible_line + i;
7133061da546Spatrick           Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get();
7134061da546Spatrick           if (!inst)
7135061da546Spatrick             break;
7136061da546Spatrick 
7137061da546Spatrick           const int line_y = m_min_y + i;
7138061da546Spatrick           window.MoveCursor(1, line_y);
7139061da546Spatrick           const bool is_pc_line = frame_sp && inst_idx == pc_idx;
7140061da546Spatrick           const bool line_is_selected = m_selected_line == inst_idx;
7141*f6aab3d8Srobert           // Highlight the line as the PC line first, then if the selected
7142*f6aab3d8Srobert           // line isn't the same as the PC line, highlight it differently
7143061da546Spatrick           attr_t highlight_attr = 0;
7144061da546Spatrick           attr_t bp_attr = 0;
7145061da546Spatrick           if (is_pc_line)
7146061da546Spatrick             highlight_attr = pc_highlight_attr;
7147061da546Spatrick           else if (line_is_selected)
7148061da546Spatrick             highlight_attr = selected_highlight_attr;
7149061da546Spatrick 
7150061da546Spatrick           if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) !=
7151061da546Spatrick               bp_file_addrs.end())
7152be691f3bSpatrick             bp_attr = COLOR_PAIR(BlackOnWhite);
7153061da546Spatrick 
7154061da546Spatrick           if (bp_attr)
7155061da546Spatrick             window.AttributeOn(bp_attr);
7156061da546Spatrick 
7157061da546Spatrick           window.Printf(" 0x%16.16llx ",
7158061da546Spatrick                         static_cast<unsigned long long>(
7159061da546Spatrick                             inst->GetAddress().GetLoadAddress(target)));
7160061da546Spatrick 
7161061da546Spatrick           if (bp_attr)
7162061da546Spatrick             window.AttributeOff(bp_attr);
7163061da546Spatrick 
7164061da546Spatrick           window.PutChar(ACS_VLINE);
7165061da546Spatrick           // Mark the line with the PC with a diamond
7166061da546Spatrick           if (is_pc_line)
7167061da546Spatrick             window.PutChar(ACS_DIAMOND);
7168061da546Spatrick           else
7169061da546Spatrick             window.PutChar(' ');
7170061da546Spatrick 
7171061da546Spatrick           if (highlight_attr)
7172061da546Spatrick             window.AttributeOn(highlight_attr);
7173061da546Spatrick 
7174061da546Spatrick           const char *mnemonic = inst->GetMnemonic(&exe_ctx);
7175061da546Spatrick           const char *operands = inst->GetOperands(&exe_ctx);
7176061da546Spatrick           const char *comment = inst->GetComment(&exe_ctx);
7177061da546Spatrick 
7178061da546Spatrick           if (mnemonic != nullptr && mnemonic[0] == '\0')
7179061da546Spatrick             mnemonic = nullptr;
7180061da546Spatrick           if (operands != nullptr && operands[0] == '\0')
7181061da546Spatrick             operands = nullptr;
7182061da546Spatrick           if (comment != nullptr && comment[0] == '\0')
7183061da546Spatrick             comment = nullptr;
7184061da546Spatrick 
7185061da546Spatrick           strm.Clear();
7186061da546Spatrick 
7187061da546Spatrick           if (mnemonic != nullptr && operands != nullptr && comment != nullptr)
7188061da546Spatrick             strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment);
7189061da546Spatrick           else if (mnemonic != nullptr && operands != nullptr)
7190061da546Spatrick             strm.Printf("%-8s %s", mnemonic, operands);
7191061da546Spatrick           else if (mnemonic != nullptr)
7192061da546Spatrick             strm.Printf("%s", mnemonic);
7193061da546Spatrick 
7194061da546Spatrick           int right_pad = 1;
7195be691f3bSpatrick           window.PutCStringTruncated(
7196be691f3bSpatrick               right_pad,
7197be691f3bSpatrick               strm.GetString().substr(m_first_visible_column).data());
7198061da546Spatrick 
7199061da546Spatrick           if (is_pc_line && frame_sp &&
7200061da546Spatrick               frame_sp->GetConcreteFrameIndex() == 0) {
7201061da546Spatrick             StopInfoSP stop_info_sp;
7202061da546Spatrick             if (thread)
7203061da546Spatrick               stop_info_sp = thread->GetStopInfo();
7204061da546Spatrick             if (stop_info_sp) {
7205061da546Spatrick               const char *stop_description = stop_info_sp->GetDescription();
7206061da546Spatrick               if (stop_description && stop_description[0]) {
7207061da546Spatrick                 size_t stop_description_len = strlen(stop_description);
7208061da546Spatrick                 int desc_x = window_width - stop_description_len - 16;
7209be691f3bSpatrick                 if (desc_x - window.GetCursorX() > 0)
7210061da546Spatrick                   window.Printf("%*s", desc_x - window.GetCursorX(), "");
7211be691f3bSpatrick                 window.MoveCursor(window_width - stop_description_len - 15,
7212be691f3bSpatrick                                   line_y);
7213*f6aab3d8Srobert                 if (thread)
7214be691f3bSpatrick                   window.PrintfTruncated(1, "<<< Thread %u: %s ",
7215*f6aab3d8Srobert                                          thread->GetIndexID(),
7216*f6aab3d8Srobert                                          stop_description);
7217061da546Spatrick               }
7218061da546Spatrick             } else {
7219061da546Spatrick               window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
7220061da546Spatrick             }
7221061da546Spatrick           }
7222061da546Spatrick           if (highlight_attr)
7223061da546Spatrick             window.AttributeOff(highlight_attr);
7224061da546Spatrick         }
7225061da546Spatrick       }
7226061da546Spatrick     }
7227061da546Spatrick     return true; // Drawing handled
7228061da546Spatrick   }
7229061da546Spatrick 
GetNumLines()7230061da546Spatrick   size_t GetNumLines() {
7231061da546Spatrick     size_t num_lines = GetNumSourceLines();
7232061da546Spatrick     if (num_lines == 0)
7233061da546Spatrick       num_lines = GetNumDisassemblyLines();
7234061da546Spatrick     return num_lines;
7235061da546Spatrick   }
7236061da546Spatrick 
GetNumSourceLines() const7237061da546Spatrick   size_t GetNumSourceLines() const {
7238061da546Spatrick     if (m_file_sp)
7239061da546Spatrick       return m_file_sp->GetNumLines();
7240061da546Spatrick     return 0;
7241061da546Spatrick   }
7242061da546Spatrick 
GetNumDisassemblyLines() const7243061da546Spatrick   size_t GetNumDisassemblyLines() const {
7244061da546Spatrick     if (m_disassembly_sp)
7245061da546Spatrick       return m_disassembly_sp->GetInstructionList().GetSize();
7246061da546Spatrick     return 0;
7247061da546Spatrick   }
7248061da546Spatrick 
WindowDelegateHandleChar(Window & window,int c)7249061da546Spatrick   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
7250061da546Spatrick     const uint32_t num_visible_lines = NumVisibleLines();
7251061da546Spatrick     const size_t num_lines = GetNumLines();
7252061da546Spatrick 
7253061da546Spatrick     switch (c) {
7254061da546Spatrick     case ',':
7255061da546Spatrick     case KEY_PPAGE:
7256061da546Spatrick       // Page up key
7257061da546Spatrick       if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines)
7258061da546Spatrick         m_first_visible_line -= num_visible_lines;
7259061da546Spatrick       else
7260061da546Spatrick         m_first_visible_line = 0;
7261061da546Spatrick       m_selected_line = m_first_visible_line;
7262061da546Spatrick       return eKeyHandled;
7263061da546Spatrick 
7264061da546Spatrick     case '.':
7265061da546Spatrick     case KEY_NPAGE:
7266061da546Spatrick       // Page down key
7267061da546Spatrick       {
7268061da546Spatrick         if (m_first_visible_line + num_visible_lines < num_lines)
7269061da546Spatrick           m_first_visible_line += num_visible_lines;
7270061da546Spatrick         else if (num_lines < num_visible_lines)
7271061da546Spatrick           m_first_visible_line = 0;
7272061da546Spatrick         else
7273061da546Spatrick           m_first_visible_line = num_lines - num_visible_lines;
7274061da546Spatrick         m_selected_line = m_first_visible_line;
7275061da546Spatrick       }
7276061da546Spatrick       return eKeyHandled;
7277061da546Spatrick 
7278061da546Spatrick     case KEY_UP:
7279061da546Spatrick       if (m_selected_line > 0) {
7280061da546Spatrick         m_selected_line--;
7281061da546Spatrick         if (static_cast<size_t>(m_first_visible_line) > m_selected_line)
7282061da546Spatrick           m_first_visible_line = m_selected_line;
7283061da546Spatrick       }
7284061da546Spatrick       return eKeyHandled;
7285061da546Spatrick 
7286061da546Spatrick     case KEY_DOWN:
7287061da546Spatrick       if (m_selected_line + 1 < num_lines) {
7288061da546Spatrick         m_selected_line++;
7289061da546Spatrick         if (m_first_visible_line + num_visible_lines < m_selected_line)
7290061da546Spatrick           m_first_visible_line++;
7291061da546Spatrick       }
7292061da546Spatrick       return eKeyHandled;
7293061da546Spatrick 
7294be691f3bSpatrick     case KEY_LEFT:
7295be691f3bSpatrick       if (m_first_visible_column > 0)
7296be691f3bSpatrick         --m_first_visible_column;
7297be691f3bSpatrick       return eKeyHandled;
7298be691f3bSpatrick 
7299be691f3bSpatrick     case KEY_RIGHT:
7300be691f3bSpatrick       ++m_first_visible_column;
7301be691f3bSpatrick       return eKeyHandled;
7302be691f3bSpatrick 
7303061da546Spatrick     case '\r':
7304061da546Spatrick     case '\n':
7305061da546Spatrick     case KEY_ENTER:
7306061da546Spatrick       // Set a breakpoint and run to the line using a one shot breakpoint
7307061da546Spatrick       if (GetNumSourceLines() > 0) {
7308061da546Spatrick         ExecutionContext exe_ctx =
7309061da546Spatrick             m_debugger.GetCommandInterpreter().GetExecutionContext();
7310061da546Spatrick         if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) {
7311061da546Spatrick           BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
7312061da546Spatrick               nullptr, // Don't limit the breakpoint to certain modules
7313061da546Spatrick               m_file_sp->GetFileSpec(), // Source file
7314061da546Spatrick               m_selected_line +
7315061da546Spatrick                   1, // Source line number (m_selected_line is zero based)
7316061da546Spatrick               0,     // Unspecified column.
7317061da546Spatrick               0,     // No offset
7318061da546Spatrick               eLazyBoolCalculate,  // Check inlines using global setting
7319061da546Spatrick               eLazyBoolCalculate,  // Skip prologue using global setting,
7320061da546Spatrick               false,               // internal
7321061da546Spatrick               false,               // request_hardware
7322061da546Spatrick               eLazyBoolCalculate); // move_to_nearest_code
7323061da546Spatrick           // Make breakpoint one shot
7324be691f3bSpatrick           bp_sp->GetOptions().SetOneShot(true);
7325061da546Spatrick           exe_ctx.GetProcessRef().Resume();
7326061da546Spatrick         }
7327061da546Spatrick       } else if (m_selected_line < GetNumDisassemblyLines()) {
7328061da546Spatrick         const Instruction *inst = m_disassembly_sp->GetInstructionList()
7329061da546Spatrick                                       .GetInstructionAtIndex(m_selected_line)
7330061da546Spatrick                                       .get();
7331061da546Spatrick         ExecutionContext exe_ctx =
7332061da546Spatrick             m_debugger.GetCommandInterpreter().GetExecutionContext();
7333061da546Spatrick         if (exe_ctx.HasTargetScope()) {
7334061da546Spatrick           Address addr = inst->GetAddress();
7335061da546Spatrick           BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
7336061da546Spatrick               addr,   // lldb_private::Address
7337061da546Spatrick               false,  // internal
7338061da546Spatrick               false); // request_hardware
7339061da546Spatrick           // Make breakpoint one shot
7340be691f3bSpatrick           bp_sp->GetOptions().SetOneShot(true);
7341061da546Spatrick           exe_ctx.GetProcessRef().Resume();
7342061da546Spatrick         }
7343061da546Spatrick       }
7344061da546Spatrick       return eKeyHandled;
7345061da546Spatrick 
7346061da546Spatrick     case 'b': // 'b' == toggle breakpoint on currently selected line
7347be691f3bSpatrick       ToggleBreakpointOnSelectedLine();
7348061da546Spatrick       return eKeyHandled;
7349061da546Spatrick 
7350061da546Spatrick     case 'D': // 'D' == detach and keep stopped
7351061da546Spatrick     {
7352061da546Spatrick       ExecutionContext exe_ctx =
7353061da546Spatrick           m_debugger.GetCommandInterpreter().GetExecutionContext();
7354061da546Spatrick       if (exe_ctx.HasProcessScope())
7355be691f3bSpatrick         exe_ctx.GetProcessRef().Detach(true);
7356061da546Spatrick     }
7357061da546Spatrick       return eKeyHandled;
7358061da546Spatrick 
7359061da546Spatrick     case 'c':
7360061da546Spatrick       // 'c' == continue
7361061da546Spatrick       {
7362061da546Spatrick         ExecutionContext exe_ctx =
7363061da546Spatrick             m_debugger.GetCommandInterpreter().GetExecutionContext();
7364061da546Spatrick         if (exe_ctx.HasProcessScope())
7365061da546Spatrick           exe_ctx.GetProcessRef().Resume();
7366061da546Spatrick       }
7367061da546Spatrick       return eKeyHandled;
7368061da546Spatrick 
7369be691f3bSpatrick     case 'f':
7370be691f3bSpatrick       // 'f' == step out (finish)
7371061da546Spatrick       {
7372061da546Spatrick         ExecutionContext exe_ctx =
7373061da546Spatrick             m_debugger.GetCommandInterpreter().GetExecutionContext();
7374061da546Spatrick         if (exe_ctx.HasThreadScope() &&
7375061da546Spatrick             StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
7376*f6aab3d8Srobert           Thread *thread = exe_ctx.GetThreadPtr();
7377*f6aab3d8Srobert           uint32_t frame_idx = thread->GetSelectedFrameIndex();
7378*f6aab3d8Srobert           exe_ctx.GetThreadRef().StepOut(frame_idx);
7379061da546Spatrick         }
7380061da546Spatrick       }
7381061da546Spatrick       return eKeyHandled;
7382061da546Spatrick 
7383061da546Spatrick     case 'n': // 'n' == step over
7384061da546Spatrick     case 'N': // 'N' == step over instruction
7385061da546Spatrick     {
7386061da546Spatrick       ExecutionContext exe_ctx =
7387061da546Spatrick           m_debugger.GetCommandInterpreter().GetExecutionContext();
7388061da546Spatrick       if (exe_ctx.HasThreadScope() &&
7389061da546Spatrick           StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
7390061da546Spatrick         bool source_step = (c == 'n');
7391061da546Spatrick         exe_ctx.GetThreadRef().StepOver(source_step);
7392061da546Spatrick       }
7393061da546Spatrick     }
7394061da546Spatrick       return eKeyHandled;
7395061da546Spatrick 
7396061da546Spatrick     case 's': // 's' == step into
7397061da546Spatrick     case 'S': // 'S' == step into instruction
7398061da546Spatrick     {
7399061da546Spatrick       ExecutionContext exe_ctx =
7400061da546Spatrick           m_debugger.GetCommandInterpreter().GetExecutionContext();
7401061da546Spatrick       if (exe_ctx.HasThreadScope() &&
7402061da546Spatrick           StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
7403061da546Spatrick         bool source_step = (c == 's');
7404061da546Spatrick         exe_ctx.GetThreadRef().StepIn(source_step);
7405061da546Spatrick       }
7406061da546Spatrick     }
7407061da546Spatrick       return eKeyHandled;
7408061da546Spatrick 
7409be691f3bSpatrick     case 'u': // 'u' == frame up
7410be691f3bSpatrick     case 'd': // 'd' == frame down
7411be691f3bSpatrick     {
7412be691f3bSpatrick       ExecutionContext exe_ctx =
7413be691f3bSpatrick           m_debugger.GetCommandInterpreter().GetExecutionContext();
7414be691f3bSpatrick       if (exe_ctx.HasThreadScope()) {
7415be691f3bSpatrick         Thread *thread = exe_ctx.GetThreadPtr();
7416be691f3bSpatrick         uint32_t frame_idx = thread->GetSelectedFrameIndex();
7417be691f3bSpatrick         if (frame_idx == UINT32_MAX)
7418be691f3bSpatrick           frame_idx = 0;
7419be691f3bSpatrick         if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount())
7420be691f3bSpatrick           ++frame_idx;
7421be691f3bSpatrick         else if (c == 'd' && frame_idx > 0)
7422be691f3bSpatrick           --frame_idx;
7423be691f3bSpatrick         if (thread->SetSelectedFrameByIndex(frame_idx, true))
7424be691f3bSpatrick           exe_ctx.SetFrameSP(thread->GetSelectedFrame());
7425be691f3bSpatrick       }
7426be691f3bSpatrick     }
7427be691f3bSpatrick       return eKeyHandled;
7428be691f3bSpatrick 
7429061da546Spatrick     case 'h':
7430061da546Spatrick       window.CreateHelpSubwindow();
7431061da546Spatrick       return eKeyHandled;
7432061da546Spatrick 
7433061da546Spatrick     default:
7434061da546Spatrick       break;
7435061da546Spatrick     }
7436061da546Spatrick     return eKeyNotHandled;
7437061da546Spatrick   }
7438061da546Spatrick 
ToggleBreakpointOnSelectedLine()7439be691f3bSpatrick   void ToggleBreakpointOnSelectedLine() {
7440be691f3bSpatrick     ExecutionContext exe_ctx =
7441be691f3bSpatrick         m_debugger.GetCommandInterpreter().GetExecutionContext();
7442be691f3bSpatrick     if (!exe_ctx.HasTargetScope())
7443be691f3bSpatrick       return;
7444be691f3bSpatrick     if (GetNumSourceLines() > 0) {
7445be691f3bSpatrick       // Source file breakpoint.
7446be691f3bSpatrick       BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
7447be691f3bSpatrick       const size_t num_bps = bp_list.GetSize();
7448be691f3bSpatrick       for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7449be691f3bSpatrick         BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7450be691f3bSpatrick         const size_t num_bps_locs = bp_sp->GetNumLocations();
7451be691f3bSpatrick         for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
7452be691f3bSpatrick           BreakpointLocationSP bp_loc_sp =
7453be691f3bSpatrick               bp_sp->GetLocationAtIndex(bp_loc_idx);
7454be691f3bSpatrick           LineEntry bp_loc_line_entry;
7455be691f3bSpatrick           if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
7456be691f3bSpatrick                   bp_loc_line_entry)) {
7457be691f3bSpatrick             if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file &&
7458be691f3bSpatrick                 m_selected_line + 1 == bp_loc_line_entry.line) {
7459be691f3bSpatrick               bool removed =
7460be691f3bSpatrick                   exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
7461be691f3bSpatrick               assert(removed);
7462be691f3bSpatrick               UNUSED_IF_ASSERT_DISABLED(removed);
7463be691f3bSpatrick               return; // Existing breakpoint removed.
7464be691f3bSpatrick             }
7465be691f3bSpatrick           }
7466be691f3bSpatrick         }
7467be691f3bSpatrick       }
7468be691f3bSpatrick       // No breakpoint found on the location, add it.
7469be691f3bSpatrick       BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
7470be691f3bSpatrick           nullptr, // Don't limit the breakpoint to certain modules
7471be691f3bSpatrick           m_file_sp->GetFileSpec(), // Source file
7472be691f3bSpatrick           m_selected_line +
7473be691f3bSpatrick               1, // Source line number (m_selected_line is zero based)
7474be691f3bSpatrick           0,     // No column specified.
7475be691f3bSpatrick           0,     // No offset
7476be691f3bSpatrick           eLazyBoolCalculate,  // Check inlines using global setting
7477be691f3bSpatrick           eLazyBoolCalculate,  // Skip prologue using global setting,
7478be691f3bSpatrick           false,               // internal
7479be691f3bSpatrick           false,               // request_hardware
7480be691f3bSpatrick           eLazyBoolCalculate); // move_to_nearest_code
7481be691f3bSpatrick     } else {
7482be691f3bSpatrick       // Disassembly breakpoint.
7483be691f3bSpatrick       assert(GetNumDisassemblyLines() > 0);
7484be691f3bSpatrick       assert(m_selected_line < GetNumDisassemblyLines());
7485be691f3bSpatrick       const Instruction *inst = m_disassembly_sp->GetInstructionList()
7486be691f3bSpatrick                                     .GetInstructionAtIndex(m_selected_line)
7487be691f3bSpatrick                                     .get();
7488be691f3bSpatrick       Address addr = inst->GetAddress();
7489be691f3bSpatrick       // Try to find it.
7490be691f3bSpatrick       BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
7491be691f3bSpatrick       const size_t num_bps = bp_list.GetSize();
7492be691f3bSpatrick       for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7493be691f3bSpatrick         BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7494be691f3bSpatrick         const size_t num_bps_locs = bp_sp->GetNumLocations();
7495be691f3bSpatrick         for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
7496be691f3bSpatrick           BreakpointLocationSP bp_loc_sp =
7497be691f3bSpatrick               bp_sp->GetLocationAtIndex(bp_loc_idx);
7498be691f3bSpatrick           LineEntry bp_loc_line_entry;
7499be691f3bSpatrick           const lldb::addr_t file_addr =
7500be691f3bSpatrick               bp_loc_sp->GetAddress().GetFileAddress();
7501be691f3bSpatrick           if (file_addr == addr.GetFileAddress()) {
7502be691f3bSpatrick             bool removed =
7503be691f3bSpatrick                 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
7504be691f3bSpatrick             assert(removed);
7505be691f3bSpatrick             UNUSED_IF_ASSERT_DISABLED(removed);
7506be691f3bSpatrick             return; // Existing breakpoint removed.
7507be691f3bSpatrick           }
7508be691f3bSpatrick         }
7509be691f3bSpatrick       }
7510be691f3bSpatrick       // No breakpoint found on the address, add it.
7511be691f3bSpatrick       BreakpointSP bp_sp =
7512be691f3bSpatrick           exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address
7513be691f3bSpatrick                                                   false,  // internal
7514be691f3bSpatrick                                                   false); // request_hardware
7515be691f3bSpatrick     }
7516be691f3bSpatrick   }
7517be691f3bSpatrick 
7518061da546Spatrick protected:
7519061da546Spatrick   typedef std::set<uint32_t> BreakpointLines;
7520061da546Spatrick   typedef std::set<lldb::addr_t> BreakpointAddrs;
7521061da546Spatrick 
7522061da546Spatrick   Debugger &m_debugger;
7523061da546Spatrick   SymbolContext m_sc;
7524061da546Spatrick   SourceManager::FileSP m_file_sp;
7525*f6aab3d8Srobert   SymbolContextScope *m_disassembly_scope = nullptr;
7526061da546Spatrick   lldb::DisassemblerSP m_disassembly_sp;
7527061da546Spatrick   AddressRange m_disassembly_range;
7528061da546Spatrick   StreamString m_title;
7529*f6aab3d8Srobert   lldb::user_id_t m_tid = LLDB_INVALID_THREAD_ID;
7530*f6aab3d8Srobert   int m_line_width = 4;
7531*f6aab3d8Srobert   uint32_t m_selected_line = 0; // The selected line
7532*f6aab3d8Srobert   uint32_t m_pc_line = 0;       // The line with the PC
7533*f6aab3d8Srobert   uint32_t m_stop_id = 0;
7534*f6aab3d8Srobert   uint32_t m_frame_idx = UINT32_MAX;
7535*f6aab3d8Srobert   int m_first_visible_line = 0;
7536*f6aab3d8Srobert   int m_first_visible_column = 0;
7537*f6aab3d8Srobert   int m_min_x = 0;
7538*f6aab3d8Srobert   int m_min_y = 0;
7539*f6aab3d8Srobert   int m_max_x = 0;
7540*f6aab3d8Srobert   int m_max_y = 0;
7541061da546Spatrick };
7542061da546Spatrick 
7543061da546Spatrick DisplayOptions ValueObjectListDelegate::g_options = {true};
7544061da546Spatrick 
IOHandlerCursesGUI(Debugger & debugger)7545061da546Spatrick IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger)
7546061da546Spatrick     : IOHandler(debugger, IOHandler::Type::Curses) {}
7547061da546Spatrick 
Activate()7548061da546Spatrick void IOHandlerCursesGUI::Activate() {
7549061da546Spatrick   IOHandler::Activate();
7550061da546Spatrick   if (!m_app_ap) {
7551dda28197Spatrick     m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE());
7552061da546Spatrick 
7553061da546Spatrick     // This is both a window and a menu delegate
7554061da546Spatrick     std::shared_ptr<ApplicationDelegate> app_delegate_sp(
7555061da546Spatrick         new ApplicationDelegate(*m_app_ap, m_debugger));
7556061da546Spatrick 
7557061da546Spatrick     MenuDelegateSP app_menu_delegate_sp =
7558061da546Spatrick         std::static_pointer_cast<MenuDelegate>(app_delegate_sp);
7559061da546Spatrick     MenuSP lldb_menu_sp(
7560061da546Spatrick         new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB));
7561061da546Spatrick     MenuSP exit_menuitem_sp(
7562061da546Spatrick         new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit));
7563061da546Spatrick     exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit);
7564061da546Spatrick     lldb_menu_sp->AddSubmenu(MenuSP(new Menu(
7565061da546Spatrick         "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout)));
7566061da546Spatrick     lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
7567061da546Spatrick     lldb_menu_sp->AddSubmenu(exit_menuitem_sp);
7568061da546Spatrick 
7569061da546Spatrick     MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2),
7570061da546Spatrick                                    ApplicationDelegate::eMenuID_Target));
7571061da546Spatrick     target_menu_sp->AddSubmenu(MenuSP(new Menu(
7572061da546Spatrick         "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate)));
7573061da546Spatrick     target_menu_sp->AddSubmenu(MenuSP(new Menu(
7574061da546Spatrick         "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete)));
7575061da546Spatrick 
7576061da546Spatrick     MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3),
7577061da546Spatrick                                     ApplicationDelegate::eMenuID_Process));
7578061da546Spatrick     process_menu_sp->AddSubmenu(MenuSP(new Menu(
7579061da546Spatrick         "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach)));
7580be691f3bSpatrick     process_menu_sp->AddSubmenu(
7581be691f3bSpatrick         MenuSP(new Menu("Detach and resume", nullptr, 'd',
7582be691f3bSpatrick                         ApplicationDelegate::eMenuID_ProcessDetachResume)));
7583be691f3bSpatrick     process_menu_sp->AddSubmenu(
7584be691f3bSpatrick         MenuSP(new Menu("Detach suspended", nullptr, 's',
7585be691f3bSpatrick                         ApplicationDelegate::eMenuID_ProcessDetachSuspended)));
7586061da546Spatrick     process_menu_sp->AddSubmenu(MenuSP(new Menu(
7587061da546Spatrick         "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch)));
7588061da546Spatrick     process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
7589061da546Spatrick     process_menu_sp->AddSubmenu(
7590061da546Spatrick         MenuSP(new Menu("Continue", nullptr, 'c',
7591061da546Spatrick                         ApplicationDelegate::eMenuID_ProcessContinue)));
7592061da546Spatrick     process_menu_sp->AddSubmenu(MenuSP(new Menu(
7593061da546Spatrick         "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt)));
7594061da546Spatrick     process_menu_sp->AddSubmenu(MenuSP(new Menu(
7595061da546Spatrick         "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill)));
7596061da546Spatrick 
7597061da546Spatrick     MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4),
7598061da546Spatrick                                    ApplicationDelegate::eMenuID_Thread));
7599061da546Spatrick     thread_menu_sp->AddSubmenu(MenuSP(new Menu(
7600061da546Spatrick         "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn)));
7601061da546Spatrick     thread_menu_sp->AddSubmenu(
7602061da546Spatrick         MenuSP(new Menu("Step Over", nullptr, 'v',
7603061da546Spatrick                         ApplicationDelegate::eMenuID_ThreadStepOver)));
7604061da546Spatrick     thread_menu_sp->AddSubmenu(MenuSP(new Menu(
7605061da546Spatrick         "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut)));
7606061da546Spatrick 
7607061da546Spatrick     MenuSP view_menu_sp(
7608061da546Spatrick         new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View));
7609061da546Spatrick     view_menu_sp->AddSubmenu(
7610*f6aab3d8Srobert         MenuSP(new Menu("Backtrace", nullptr, 't',
7611061da546Spatrick                         ApplicationDelegate::eMenuID_ViewBacktrace)));
7612061da546Spatrick     view_menu_sp->AddSubmenu(
7613061da546Spatrick         MenuSP(new Menu("Registers", nullptr, 'r',
7614061da546Spatrick                         ApplicationDelegate::eMenuID_ViewRegisters)));
7615061da546Spatrick     view_menu_sp->AddSubmenu(MenuSP(new Menu(
7616061da546Spatrick         "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource)));
7617061da546Spatrick     view_menu_sp->AddSubmenu(
7618061da546Spatrick         MenuSP(new Menu("Variables", nullptr, 'v',
7619061da546Spatrick                         ApplicationDelegate::eMenuID_ViewVariables)));
7620*f6aab3d8Srobert     view_menu_sp->AddSubmenu(
7621*f6aab3d8Srobert         MenuSP(new Menu("Breakpoints", nullptr, 'b',
7622*f6aab3d8Srobert                         ApplicationDelegate::eMenuID_ViewBreakpoints)));
7623061da546Spatrick 
7624061da546Spatrick     MenuSP help_menu_sp(
7625061da546Spatrick         new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help));
7626061da546Spatrick     help_menu_sp->AddSubmenu(MenuSP(new Menu(
7627061da546Spatrick         "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp)));
7628061da546Spatrick 
7629061da546Spatrick     m_app_ap->Initialize();
7630061da546Spatrick     WindowSP &main_window_sp = m_app_ap->GetMainWindow();
7631061da546Spatrick 
7632061da546Spatrick     MenuSP menubar_sp(new Menu(Menu::Type::Bar));
7633061da546Spatrick     menubar_sp->AddSubmenu(lldb_menu_sp);
7634061da546Spatrick     menubar_sp->AddSubmenu(target_menu_sp);
7635061da546Spatrick     menubar_sp->AddSubmenu(process_menu_sp);
7636061da546Spatrick     menubar_sp->AddSubmenu(thread_menu_sp);
7637061da546Spatrick     menubar_sp->AddSubmenu(view_menu_sp);
7638061da546Spatrick     menubar_sp->AddSubmenu(help_menu_sp);
7639061da546Spatrick     menubar_sp->SetDelegate(app_menu_delegate_sp);
7640061da546Spatrick 
7641061da546Spatrick     Rect content_bounds = main_window_sp->GetFrame();
7642061da546Spatrick     Rect menubar_bounds = content_bounds.MakeMenuBar();
7643061da546Spatrick     Rect status_bounds = content_bounds.MakeStatusBar();
7644061da546Spatrick     Rect source_bounds;
7645061da546Spatrick     Rect variables_bounds;
7646061da546Spatrick     Rect threads_bounds;
7647061da546Spatrick     Rect source_variables_bounds;
7648061da546Spatrick     content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
7649061da546Spatrick                                            threads_bounds);
7650061da546Spatrick     source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds,
7651061da546Spatrick                                                       variables_bounds);
7652061da546Spatrick 
7653061da546Spatrick     WindowSP menubar_window_sp =
7654061da546Spatrick         main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false);
7655061da546Spatrick     // Let the menubar get keys if the active window doesn't handle the keys
7656061da546Spatrick     // that are typed so it can respond to menubar key presses.
7657061da546Spatrick     menubar_window_sp->SetCanBeActive(
7658061da546Spatrick         false); // Don't let the menubar become the active window
7659061da546Spatrick     menubar_window_sp->SetDelegate(menubar_sp);
7660061da546Spatrick 
7661061da546Spatrick     WindowSP source_window_sp(
7662061da546Spatrick         main_window_sp->CreateSubWindow("Source", source_bounds, true));
7663061da546Spatrick     WindowSP variables_window_sp(
7664061da546Spatrick         main_window_sp->CreateSubWindow("Variables", variables_bounds, false));
7665061da546Spatrick     WindowSP threads_window_sp(
7666061da546Spatrick         main_window_sp->CreateSubWindow("Threads", threads_bounds, false));
7667061da546Spatrick     WindowSP status_window_sp(
7668061da546Spatrick         main_window_sp->CreateSubWindow("Status", status_bounds, false));
7669061da546Spatrick     status_window_sp->SetCanBeActive(
7670061da546Spatrick         false); // Don't let the status bar become the active window
7671061da546Spatrick     main_window_sp->SetDelegate(
7672061da546Spatrick         std::static_pointer_cast<WindowDelegate>(app_delegate_sp));
7673061da546Spatrick     source_window_sp->SetDelegate(
7674061da546Spatrick         WindowDelegateSP(new SourceFileWindowDelegate(m_debugger)));
7675061da546Spatrick     variables_window_sp->SetDelegate(
7676061da546Spatrick         WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
7677061da546Spatrick     TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger));
7678061da546Spatrick     threads_window_sp->SetDelegate(WindowDelegateSP(
7679061da546Spatrick         new TreeWindowDelegate(m_debugger, thread_delegate_sp)));
7680061da546Spatrick     status_window_sp->SetDelegate(
7681061da546Spatrick         WindowDelegateSP(new StatusBarWindowDelegate(m_debugger)));
7682061da546Spatrick 
7683be691f3bSpatrick     // All colors with black background.
7684be691f3bSpatrick     init_pair(1, COLOR_BLACK, COLOR_BLACK);
7685be691f3bSpatrick     init_pair(2, COLOR_RED, COLOR_BLACK);
7686be691f3bSpatrick     init_pair(3, COLOR_GREEN, COLOR_BLACK);
7687be691f3bSpatrick     init_pair(4, COLOR_YELLOW, COLOR_BLACK);
7688be691f3bSpatrick     init_pair(5, COLOR_BLUE, COLOR_BLACK);
7689be691f3bSpatrick     init_pair(6, COLOR_MAGENTA, COLOR_BLACK);
7690be691f3bSpatrick     init_pair(7, COLOR_CYAN, COLOR_BLACK);
7691be691f3bSpatrick     init_pair(8, COLOR_WHITE, COLOR_BLACK);
7692be691f3bSpatrick     // All colors with blue background.
7693be691f3bSpatrick     init_pair(9, COLOR_BLACK, COLOR_BLUE);
7694be691f3bSpatrick     init_pair(10, COLOR_RED, COLOR_BLUE);
7695be691f3bSpatrick     init_pair(11, COLOR_GREEN, COLOR_BLUE);
7696be691f3bSpatrick     init_pair(12, COLOR_YELLOW, COLOR_BLUE);
7697be691f3bSpatrick     init_pair(13, COLOR_BLUE, COLOR_BLUE);
7698be691f3bSpatrick     init_pair(14, COLOR_MAGENTA, COLOR_BLUE);
7699be691f3bSpatrick     init_pair(15, COLOR_CYAN, COLOR_BLUE);
7700be691f3bSpatrick     init_pair(16, COLOR_WHITE, COLOR_BLUE);
7701be691f3bSpatrick     // These must match the order in the color indexes enum.
7702be691f3bSpatrick     init_pair(17, COLOR_BLACK, COLOR_WHITE);
7703be691f3bSpatrick     init_pair(18, COLOR_MAGENTA, COLOR_WHITE);
7704be691f3bSpatrick     static_assert(LastColorPairIndex == 18, "Color indexes do not match.");
7705be691f3bSpatrick 
7706be691f3bSpatrick     define_key("\033[Z", KEY_SHIFT_TAB);
7707*f6aab3d8Srobert     define_key("\033\015", KEY_ALT_ENTER);
7708061da546Spatrick   }
7709061da546Spatrick }
7710061da546Spatrick 
Deactivate()7711061da546Spatrick void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); }
7712061da546Spatrick 
Run()7713061da546Spatrick void IOHandlerCursesGUI::Run() {
7714061da546Spatrick   m_app_ap->Run(m_debugger);
7715061da546Spatrick   SetIsDone(true);
7716061da546Spatrick }
7717061da546Spatrick 
7718061da546Spatrick IOHandlerCursesGUI::~IOHandlerCursesGUI() = default;
7719061da546Spatrick 
Cancel()7720061da546Spatrick void IOHandlerCursesGUI::Cancel() {}
7721061da546Spatrick 
Interrupt()7722*f6aab3d8Srobert bool IOHandlerCursesGUI::Interrupt() {
7723*f6aab3d8Srobert   return m_debugger.GetCommandInterpreter().IOHandlerInterrupt(*this);
7724*f6aab3d8Srobert }
7725061da546Spatrick 
GotEOF()7726061da546Spatrick void IOHandlerCursesGUI::GotEOF() {}
7727061da546Spatrick 
TerminalSizeChanged()7728be691f3bSpatrick void IOHandlerCursesGUI::TerminalSizeChanged() {
7729be691f3bSpatrick   m_app_ap->TerminalSizeChanged();
7730be691f3bSpatrick }
7731be691f3bSpatrick 
7732061da546Spatrick #endif // LLDB_ENABLE_CURSES
7733