15ffd83dbSDimitry Andric //===-- IOHandlerCursesGUI.cpp --------------------------------------------===//
2480093f4SDimitry Andric //
3480093f4SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4480093f4SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5480093f4SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6480093f4SDimitry Andric //
7480093f4SDimitry Andric //===----------------------------------------------------------------------===//
8480093f4SDimitry Andric 
9480093f4SDimitry Andric #include "lldb/Core/IOHandlerCursesGUI.h"
10480093f4SDimitry Andric #include "lldb/Host/Config.h"
11480093f4SDimitry Andric 
12480093f4SDimitry Andric #if LLDB_ENABLE_CURSES
13af732203SDimitry Andric #if CURSES_HAVE_NCURSES_CURSES_H
14af732203SDimitry Andric #include <ncurses/curses.h>
15af732203SDimitry Andric #include <ncurses/panel.h>
16af732203SDimitry Andric #else
17480093f4SDimitry Andric #include <curses.h>
18480093f4SDimitry Andric #include <panel.h>
19480093f4SDimitry Andric #endif
20af732203SDimitry Andric #endif
21480093f4SDimitry Andric 
22480093f4SDimitry Andric #if defined(__APPLE__)
23480093f4SDimitry Andric #include <deque>
24480093f4SDimitry Andric #endif
25480093f4SDimitry Andric #include <string>
26480093f4SDimitry Andric 
27480093f4SDimitry Andric #include "lldb/Core/Debugger.h"
28480093f4SDimitry Andric #include "lldb/Core/StreamFile.h"
29*5f7ddb14SDimitry Andric #include "lldb/Core/ValueObjectUpdater.h"
30480093f4SDimitry Andric #include "lldb/Host/File.h"
31480093f4SDimitry Andric #include "lldb/Utility/Predicate.h"
32480093f4SDimitry Andric #include "lldb/Utility/Status.h"
33480093f4SDimitry Andric #include "lldb/Utility/StreamString.h"
34480093f4SDimitry Andric #include "lldb/Utility/StringList.h"
35480093f4SDimitry Andric #include "lldb/lldb-forward.h"
36480093f4SDimitry Andric 
37480093f4SDimitry Andric #include "lldb/Interpreter/CommandCompletions.h"
38480093f4SDimitry Andric #include "lldb/Interpreter/CommandInterpreter.h"
39480093f4SDimitry Andric 
40480093f4SDimitry Andric #if LLDB_ENABLE_CURSES
41480093f4SDimitry Andric #include "lldb/Breakpoint/BreakpointLocation.h"
42480093f4SDimitry Andric #include "lldb/Core/Module.h"
43*5f7ddb14SDimitry Andric #include "lldb/Core/PluginManager.h"
44480093f4SDimitry Andric #include "lldb/Core/ValueObject.h"
45480093f4SDimitry Andric #include "lldb/Core/ValueObjectRegister.h"
46480093f4SDimitry Andric #include "lldb/Symbol/Block.h"
47480093f4SDimitry Andric #include "lldb/Symbol/Function.h"
48480093f4SDimitry Andric #include "lldb/Symbol/Symbol.h"
49480093f4SDimitry Andric #include "lldb/Symbol/VariableList.h"
50480093f4SDimitry Andric #include "lldb/Target/Process.h"
51480093f4SDimitry Andric #include "lldb/Target/RegisterContext.h"
52480093f4SDimitry Andric #include "lldb/Target/StackFrame.h"
53480093f4SDimitry Andric #include "lldb/Target/StopInfo.h"
54480093f4SDimitry Andric #include "lldb/Target/Target.h"
55480093f4SDimitry Andric #include "lldb/Target/Thread.h"
56480093f4SDimitry Andric #include "lldb/Utility/State.h"
57480093f4SDimitry Andric #endif
58480093f4SDimitry Andric 
59480093f4SDimitry Andric #include "llvm/ADT/StringRef.h"
60480093f4SDimitry Andric 
61480093f4SDimitry Andric #ifdef _WIN32
62480093f4SDimitry Andric #include "lldb/Host/windows/windows.h"
63480093f4SDimitry Andric #endif
64480093f4SDimitry Andric 
65480093f4SDimitry Andric #include <memory>
66480093f4SDimitry Andric #include <mutex>
67480093f4SDimitry Andric 
68*5f7ddb14SDimitry Andric #include <cassert>
69*5f7ddb14SDimitry Andric #include <cctype>
70*5f7ddb14SDimitry Andric #include <cerrno>
71*5f7ddb14SDimitry Andric #include <cstdint>
72*5f7ddb14SDimitry Andric #include <cstdio>
73*5f7ddb14SDimitry Andric #include <cstring>
74*5f7ddb14SDimitry Andric #include <functional>
75480093f4SDimitry Andric #include <type_traits>
76480093f4SDimitry Andric 
77480093f4SDimitry Andric using namespace lldb;
78480093f4SDimitry Andric using namespace lldb_private;
79480093f4SDimitry Andric using llvm::None;
80480093f4SDimitry Andric using llvm::Optional;
81480093f4SDimitry Andric using llvm::StringRef;
82480093f4SDimitry Andric 
83480093f4SDimitry Andric // we may want curses to be disabled for some builds for instance, windows
84480093f4SDimitry Andric #if LLDB_ENABLE_CURSES
85480093f4SDimitry Andric 
86480093f4SDimitry Andric #define KEY_RETURN 10
87480093f4SDimitry Andric #define KEY_ESCAPE 27
88480093f4SDimitry Andric 
89*5f7ddb14SDimitry Andric #define KEY_SHIFT_TAB (KEY_MAX + 1)
90*5f7ddb14SDimitry Andric 
91480093f4SDimitry Andric namespace curses {
92480093f4SDimitry Andric class Menu;
93480093f4SDimitry Andric class MenuDelegate;
94480093f4SDimitry Andric class Window;
95480093f4SDimitry Andric class WindowDelegate;
96480093f4SDimitry Andric typedef std::shared_ptr<Menu> MenuSP;
97480093f4SDimitry Andric typedef std::shared_ptr<MenuDelegate> MenuDelegateSP;
98480093f4SDimitry Andric typedef std::shared_ptr<Window> WindowSP;
99480093f4SDimitry Andric typedef std::shared_ptr<WindowDelegate> WindowDelegateSP;
100480093f4SDimitry Andric typedef std::vector<MenuSP> Menus;
101480093f4SDimitry Andric typedef std::vector<WindowSP> Windows;
102480093f4SDimitry Andric typedef std::vector<WindowDelegateSP> WindowDelegates;
103480093f4SDimitry Andric 
104480093f4SDimitry Andric #if 0
105480093f4SDimitry Andric type summary add -s "x=${var.x}, y=${var.y}" curses::Point
106480093f4SDimitry Andric type summary add -s "w=${var.width}, h=${var.height}" curses::Size
107480093f4SDimitry Andric type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect
108480093f4SDimitry Andric #endif
109480093f4SDimitry Andric 
110480093f4SDimitry Andric struct Point {
111480093f4SDimitry Andric   int x;
112480093f4SDimitry Andric   int y;
113480093f4SDimitry Andric 
Pointcurses::Point114480093f4SDimitry Andric   Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}
115480093f4SDimitry Andric 
Clearcurses::Point116480093f4SDimitry Andric   void Clear() {
117480093f4SDimitry Andric     x = 0;
118480093f4SDimitry Andric     y = 0;
119480093f4SDimitry Andric   }
120480093f4SDimitry Andric 
operator +=curses::Point121480093f4SDimitry Andric   Point &operator+=(const Point &rhs) {
122480093f4SDimitry Andric     x += rhs.x;
123480093f4SDimitry Andric     y += rhs.y;
124480093f4SDimitry Andric     return *this;
125480093f4SDimitry Andric   }
126480093f4SDimitry Andric 
Dumpcurses::Point127480093f4SDimitry Andric   void Dump() { printf("(x=%i, y=%i)\n", x, y); }
128480093f4SDimitry Andric };
129480093f4SDimitry Andric 
operator ==(const Point & lhs,const Point & rhs)130480093f4SDimitry Andric bool operator==(const Point &lhs, const Point &rhs) {
131480093f4SDimitry Andric   return lhs.x == rhs.x && lhs.y == rhs.y;
132480093f4SDimitry Andric }
133480093f4SDimitry Andric 
operator !=(const Point & lhs,const Point & rhs)134480093f4SDimitry Andric bool operator!=(const Point &lhs, const Point &rhs) {
135480093f4SDimitry Andric   return lhs.x != rhs.x || lhs.y != rhs.y;
136480093f4SDimitry Andric }
137480093f4SDimitry Andric 
138480093f4SDimitry Andric struct Size {
139480093f4SDimitry Andric   int width;
140480093f4SDimitry Andric   int height;
Sizecurses::Size141480093f4SDimitry Andric   Size(int w = 0, int h = 0) : width(w), height(h) {}
142480093f4SDimitry Andric 
Clearcurses::Size143480093f4SDimitry Andric   void Clear() {
144480093f4SDimitry Andric     width = 0;
145480093f4SDimitry Andric     height = 0;
146480093f4SDimitry Andric   }
147480093f4SDimitry Andric 
Dumpcurses::Size148480093f4SDimitry Andric   void Dump() { printf("(w=%i, h=%i)\n", width, height); }
149480093f4SDimitry Andric };
150480093f4SDimitry Andric 
operator ==(const Size & lhs,const Size & rhs)151480093f4SDimitry Andric bool operator==(const Size &lhs, const Size &rhs) {
152480093f4SDimitry Andric   return lhs.width == rhs.width && lhs.height == rhs.height;
153480093f4SDimitry Andric }
154480093f4SDimitry Andric 
operator !=(const Size & lhs,const Size & rhs)155480093f4SDimitry Andric bool operator!=(const Size &lhs, const Size &rhs) {
156480093f4SDimitry Andric   return lhs.width != rhs.width || lhs.height != rhs.height;
157480093f4SDimitry Andric }
158480093f4SDimitry Andric 
159480093f4SDimitry Andric struct Rect {
160480093f4SDimitry Andric   Point origin;
161480093f4SDimitry Andric   Size size;
162480093f4SDimitry Andric 
Rectcurses::Rect163480093f4SDimitry Andric   Rect() : origin(), size() {}
164480093f4SDimitry Andric 
Rectcurses::Rect165480093f4SDimitry Andric   Rect(const Point &p, const Size &s) : origin(p), size(s) {}
166480093f4SDimitry Andric 
Clearcurses::Rect167480093f4SDimitry Andric   void Clear() {
168480093f4SDimitry Andric     origin.Clear();
169480093f4SDimitry Andric     size.Clear();
170480093f4SDimitry Andric   }
171480093f4SDimitry Andric 
Dumpcurses::Rect172480093f4SDimitry Andric   void Dump() {
173480093f4SDimitry Andric     printf("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width,
174480093f4SDimitry Andric            size.height);
175480093f4SDimitry Andric   }
176480093f4SDimitry Andric 
Insetcurses::Rect177480093f4SDimitry Andric   void Inset(int w, int h) {
178480093f4SDimitry Andric     if (size.width > w * 2)
179480093f4SDimitry Andric       size.width -= w * 2;
180480093f4SDimitry Andric     origin.x += w;
181480093f4SDimitry Andric 
182480093f4SDimitry Andric     if (size.height > h * 2)
183480093f4SDimitry Andric       size.height -= h * 2;
184480093f4SDimitry Andric     origin.y += h;
185480093f4SDimitry Andric   }
186480093f4SDimitry Andric 
187480093f4SDimitry Andric   // Return a status bar rectangle which is the last line of this rectangle.
188480093f4SDimitry Andric   // This rectangle will be modified to not include the status bar area.
MakeStatusBarcurses::Rect189480093f4SDimitry Andric   Rect MakeStatusBar() {
190480093f4SDimitry Andric     Rect status_bar;
191480093f4SDimitry Andric     if (size.height > 1) {
192480093f4SDimitry Andric       status_bar.origin.x = origin.x;
193480093f4SDimitry Andric       status_bar.origin.y = size.height;
194480093f4SDimitry Andric       status_bar.size.width = size.width;
195480093f4SDimitry Andric       status_bar.size.height = 1;
196480093f4SDimitry Andric       --size.height;
197480093f4SDimitry Andric     }
198480093f4SDimitry Andric     return status_bar;
199480093f4SDimitry Andric   }
200480093f4SDimitry Andric 
201480093f4SDimitry Andric   // Return a menubar rectangle which is the first line of this rectangle. This
202480093f4SDimitry Andric   // rectangle will be modified to not include the menubar area.
MakeMenuBarcurses::Rect203480093f4SDimitry Andric   Rect MakeMenuBar() {
204480093f4SDimitry Andric     Rect menubar;
205480093f4SDimitry Andric     if (size.height > 1) {
206480093f4SDimitry Andric       menubar.origin.x = origin.x;
207480093f4SDimitry Andric       menubar.origin.y = origin.y;
208480093f4SDimitry Andric       menubar.size.width = size.width;
209480093f4SDimitry Andric       menubar.size.height = 1;
210480093f4SDimitry Andric       ++origin.y;
211480093f4SDimitry Andric       --size.height;
212480093f4SDimitry Andric     }
213480093f4SDimitry Andric     return menubar;
214480093f4SDimitry Andric   }
215480093f4SDimitry Andric 
HorizontalSplitPercentagecurses::Rect216480093f4SDimitry Andric   void HorizontalSplitPercentage(float top_percentage, Rect &top,
217480093f4SDimitry Andric                                  Rect &bottom) const {
218480093f4SDimitry Andric     float top_height = top_percentage * size.height;
219480093f4SDimitry Andric     HorizontalSplit(top_height, top, bottom);
220480093f4SDimitry Andric   }
221480093f4SDimitry Andric 
HorizontalSplitcurses::Rect222480093f4SDimitry Andric   void HorizontalSplit(int top_height, Rect &top, Rect &bottom) const {
223480093f4SDimitry Andric     top = *this;
224480093f4SDimitry Andric     if (top_height < size.height) {
225480093f4SDimitry Andric       top.size.height = top_height;
226480093f4SDimitry Andric       bottom.origin.x = origin.x;
227480093f4SDimitry Andric       bottom.origin.y = origin.y + top.size.height;
228480093f4SDimitry Andric       bottom.size.width = size.width;
229480093f4SDimitry Andric       bottom.size.height = size.height - top.size.height;
230480093f4SDimitry Andric     } else {
231480093f4SDimitry Andric       bottom.Clear();
232480093f4SDimitry Andric     }
233480093f4SDimitry Andric   }
234480093f4SDimitry Andric 
VerticalSplitPercentagecurses::Rect235480093f4SDimitry Andric   void VerticalSplitPercentage(float left_percentage, Rect &left,
236480093f4SDimitry Andric                                Rect &right) const {
237480093f4SDimitry Andric     float left_width = left_percentage * size.width;
238480093f4SDimitry Andric     VerticalSplit(left_width, left, right);
239480093f4SDimitry Andric   }
240480093f4SDimitry Andric 
VerticalSplitcurses::Rect241480093f4SDimitry Andric   void VerticalSplit(int left_width, Rect &left, Rect &right) const {
242480093f4SDimitry Andric     left = *this;
243480093f4SDimitry Andric     if (left_width < size.width) {
244480093f4SDimitry Andric       left.size.width = left_width;
245480093f4SDimitry Andric       right.origin.x = origin.x + left.size.width;
246480093f4SDimitry Andric       right.origin.y = origin.y;
247480093f4SDimitry Andric       right.size.width = size.width - left.size.width;
248480093f4SDimitry Andric       right.size.height = size.height;
249480093f4SDimitry Andric     } else {
250480093f4SDimitry Andric       right.Clear();
251480093f4SDimitry Andric     }
252480093f4SDimitry Andric   }
253480093f4SDimitry Andric };
254480093f4SDimitry Andric 
operator ==(const Rect & lhs,const Rect & rhs)255480093f4SDimitry Andric bool operator==(const Rect &lhs, const Rect &rhs) {
256480093f4SDimitry Andric   return lhs.origin == rhs.origin && lhs.size == rhs.size;
257480093f4SDimitry Andric }
258480093f4SDimitry Andric 
operator !=(const Rect & lhs,const Rect & rhs)259480093f4SDimitry Andric bool operator!=(const Rect &lhs, const Rect &rhs) {
260480093f4SDimitry Andric   return lhs.origin != rhs.origin || lhs.size != rhs.size;
261480093f4SDimitry Andric }
262480093f4SDimitry Andric 
263480093f4SDimitry Andric enum HandleCharResult {
264480093f4SDimitry Andric   eKeyNotHandled = 0,
265480093f4SDimitry Andric   eKeyHandled = 1,
266480093f4SDimitry Andric   eQuitApplication = 2
267480093f4SDimitry Andric };
268480093f4SDimitry Andric 
269480093f4SDimitry Andric enum class MenuActionResult {
270480093f4SDimitry Andric   Handled,
271480093f4SDimitry Andric   NotHandled,
272480093f4SDimitry Andric   Quit // Exit all menus and quit
273480093f4SDimitry Andric };
274480093f4SDimitry Andric 
275480093f4SDimitry Andric struct KeyHelp {
276480093f4SDimitry Andric   int ch;
277480093f4SDimitry Andric   const char *description;
278480093f4SDimitry Andric };
279480093f4SDimitry Andric 
280af732203SDimitry Andric // COLOR_PAIR index names
281af732203SDimitry Andric enum {
282af732203SDimitry Andric   // First 16 colors are 8 black background and 8 blue background colors,
283af732203SDimitry Andric   // needed by OutputColoredStringTruncated().
284af732203SDimitry Andric   BlackOnBlack = 1,
285af732203SDimitry Andric   RedOnBlack,
286af732203SDimitry Andric   GreenOnBlack,
287af732203SDimitry Andric   YellowOnBlack,
288af732203SDimitry Andric   BlueOnBlack,
289af732203SDimitry Andric   MagentaOnBlack,
290af732203SDimitry Andric   CyanOnBlack,
291af732203SDimitry Andric   WhiteOnBlack,
292af732203SDimitry Andric   BlackOnBlue,
293af732203SDimitry Andric   RedOnBlue,
294af732203SDimitry Andric   GreenOnBlue,
295af732203SDimitry Andric   YellowOnBlue,
296af732203SDimitry Andric   BlueOnBlue,
297af732203SDimitry Andric   MagentaOnBlue,
298af732203SDimitry Andric   CyanOnBlue,
299af732203SDimitry Andric   WhiteOnBlue,
300af732203SDimitry Andric   // Other colors, as needed.
301af732203SDimitry Andric   BlackOnWhite,
302af732203SDimitry Andric   MagentaOnWhite,
303af732203SDimitry Andric   LastColorPairIndex = MagentaOnWhite
304af732203SDimitry Andric };
305af732203SDimitry Andric 
306480093f4SDimitry Andric class WindowDelegate {
307480093f4SDimitry Andric public:
308480093f4SDimitry Andric   virtual ~WindowDelegate() = default;
309480093f4SDimitry Andric 
WindowDelegateDraw(Window & window,bool force)310480093f4SDimitry Andric   virtual bool WindowDelegateDraw(Window &window, bool force) {
311480093f4SDimitry Andric     return false; // Drawing not handled
312480093f4SDimitry Andric   }
313480093f4SDimitry Andric 
WindowDelegateHandleChar(Window & window,int key)314480093f4SDimitry Andric   virtual HandleCharResult WindowDelegateHandleChar(Window &window, int key) {
315480093f4SDimitry Andric     return eKeyNotHandled;
316480093f4SDimitry Andric   }
317480093f4SDimitry Andric 
WindowDelegateGetHelpText()318480093f4SDimitry Andric   virtual const char *WindowDelegateGetHelpText() { return nullptr; }
319480093f4SDimitry Andric 
WindowDelegateGetKeyHelp()320480093f4SDimitry Andric   virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; }
321480093f4SDimitry Andric };
322480093f4SDimitry Andric 
323480093f4SDimitry Andric class HelpDialogDelegate : public WindowDelegate {
324480093f4SDimitry Andric public:
325480093f4SDimitry Andric   HelpDialogDelegate(const char *text, KeyHelp *key_help_array);
326480093f4SDimitry Andric 
327480093f4SDimitry Andric   ~HelpDialogDelegate() override;
328480093f4SDimitry Andric 
329480093f4SDimitry Andric   bool WindowDelegateDraw(Window &window, bool force) override;
330480093f4SDimitry Andric 
331480093f4SDimitry Andric   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
332480093f4SDimitry Andric 
GetNumLines() const333480093f4SDimitry Andric   size_t GetNumLines() const { return m_text.GetSize(); }
334480093f4SDimitry Andric 
GetMaxLineLength() const335480093f4SDimitry Andric   size_t GetMaxLineLength() const { return m_text.GetMaxStringLength(); }
336480093f4SDimitry Andric 
337480093f4SDimitry Andric protected:
338480093f4SDimitry Andric   StringList m_text;
339480093f4SDimitry Andric   int m_first_visible_line;
340480093f4SDimitry Andric };
341480093f4SDimitry Andric 
342*5f7ddb14SDimitry Andric // A surface is an abstraction for something than can be drawn on. The surface
343*5f7ddb14SDimitry Andric // have a width, a height, a cursor position, and a multitude of drawing
344*5f7ddb14SDimitry Andric // operations. This type should be sub-classed to get an actually useful ncurses
345*5f7ddb14SDimitry Andric // object, such as a Window, SubWindow, Pad, or a SubPad.
346*5f7ddb14SDimitry Andric class Surface {
347480093f4SDimitry Andric public:
Surface()348*5f7ddb14SDimitry Andric   Surface() : m_window(nullptr) {}
349480093f4SDimitry Andric 
get()350*5f7ddb14SDimitry Andric   WINDOW *get() { return m_window; }
351*5f7ddb14SDimitry Andric 
operator WINDOW*()352*5f7ddb14SDimitry Andric   operator WINDOW *() { return m_window; }
353*5f7ddb14SDimitry Andric 
354*5f7ddb14SDimitry Andric   // Copy a region of the surface to another surface.
CopyToSurface(Surface & target,Point source_origin,Point target_origin,Size size)355*5f7ddb14SDimitry Andric   void CopyToSurface(Surface &target, Point source_origin, Point target_origin,
356*5f7ddb14SDimitry Andric                      Size size) {
357*5f7ddb14SDimitry Andric     ::copywin(m_window, target.get(), source_origin.y, source_origin.x,
358*5f7ddb14SDimitry Andric               target_origin.y, target_origin.x,
359*5f7ddb14SDimitry Andric               target_origin.y + size.height - 1,
360*5f7ddb14SDimitry Andric               target_origin.x + size.width - 1, false);
361480093f4SDimitry Andric   }
362480093f4SDimitry Andric 
GetCursorX() const363*5f7ddb14SDimitry Andric   int GetCursorX() const { return getcurx(m_window); }
GetCursorY() const364*5f7ddb14SDimitry Andric   int GetCursorY() const { return getcury(m_window); }
MoveCursor(int x,int y)365*5f7ddb14SDimitry Andric   void MoveCursor(int x, int y) { ::wmove(m_window, y, x); }
366480093f4SDimitry Andric 
AttributeOn(attr_t attr)367480093f4SDimitry Andric   void AttributeOn(attr_t attr) { ::wattron(m_window, attr); }
AttributeOff(attr_t attr)368480093f4SDimitry Andric   void AttributeOff(attr_t attr) { ::wattroff(m_window, attr); }
369*5f7ddb14SDimitry Andric 
GetMaxX() const370af732203SDimitry Andric   int GetMaxX() const { return getmaxx(m_window); }
GetMaxY() const371af732203SDimitry Andric   int GetMaxY() const { return getmaxy(m_window); }
GetWidth() const372af732203SDimitry Andric   int GetWidth() const { return GetMaxX(); }
GetHeight() const373af732203SDimitry Andric   int GetHeight() const { return GetMaxY(); }
GetSize() const374*5f7ddb14SDimitry Andric   Size GetSize() const { return Size(GetWidth(), GetHeight()); }
375*5f7ddb14SDimitry Andric   // Get a zero origin rectangle width the surface size.
GetFrame() const376*5f7ddb14SDimitry Andric   Rect GetFrame() const { return Rect(Point(), GetSize()); }
377*5f7ddb14SDimitry Andric 
Clear()378*5f7ddb14SDimitry Andric   void Clear() { ::wclear(m_window); }
Erase()379*5f7ddb14SDimitry Andric   void Erase() { ::werase(m_window); }
380*5f7ddb14SDimitry Andric 
SetBackground(int color_pair_idx)381480093f4SDimitry Andric   void SetBackground(int color_pair_idx) {
382480093f4SDimitry Andric     ::wbkgd(m_window, COLOR_PAIR(color_pair_idx));
383480093f4SDimitry Andric   }
384480093f4SDimitry Andric 
PutChar(int ch)385*5f7ddb14SDimitry Andric   void PutChar(int ch) { ::waddch(m_window, ch); }
PutCString(const char * s,int len=-1)386*5f7ddb14SDimitry Andric   void PutCString(const char *s, int len = -1) { ::waddnstr(m_window, s, len); }
387*5f7ddb14SDimitry Andric 
PutCStringTruncated(int right_pad,const char * s,int len=-1)388af732203SDimitry Andric   void PutCStringTruncated(int right_pad, const char *s, int len = -1) {
389480093f4SDimitry Andric     int bytes_left = GetWidth() - GetCursorX();
390480093f4SDimitry Andric     if (bytes_left > right_pad) {
391480093f4SDimitry Andric       bytes_left -= right_pad;
392af732203SDimitry Andric       ::waddnstr(m_window, s, len < 0 ? bytes_left : std::min(bytes_left, len));
393480093f4SDimitry Andric     }
394480093f4SDimitry Andric   }
395480093f4SDimitry Andric 
Printf(const char * format,...)396480093f4SDimitry Andric   void Printf(const char *format, ...) __attribute__((format(printf, 2, 3))) {
397480093f4SDimitry Andric     va_list args;
398480093f4SDimitry Andric     va_start(args, format);
399*5f7ddb14SDimitry Andric     vw_printw(m_window, format, args);
400480093f4SDimitry Andric     va_end(args);
401480093f4SDimitry Andric   }
402480093f4SDimitry Andric 
PrintfTruncated(int right_pad,const char * format,...)403af732203SDimitry Andric   void PrintfTruncated(int right_pad, const char *format, ...)
404af732203SDimitry Andric       __attribute__((format(printf, 3, 4))) {
405af732203SDimitry Andric     va_list args;
406af732203SDimitry Andric     va_start(args, format);
407af732203SDimitry Andric     StreamString strm;
408af732203SDimitry Andric     strm.PrintfVarArg(format, args);
409af732203SDimitry Andric     va_end(args);
410af732203SDimitry Andric     PutCStringTruncated(right_pad, strm.GetData());
411af732203SDimitry Andric   }
412af732203SDimitry Andric 
VerticalLine(int n,chtype v_char=ACS_VLINE)413*5f7ddb14SDimitry Andric   void VerticalLine(int n, chtype v_char = ACS_VLINE) {
414*5f7ddb14SDimitry Andric     ::wvline(m_window, v_char, n);
415*5f7ddb14SDimitry Andric   }
HorizontalLine(int n,chtype h_char=ACS_HLINE)416*5f7ddb14SDimitry Andric   void HorizontalLine(int n, chtype h_char = ACS_HLINE) {
417*5f7ddb14SDimitry Andric     ::whline(m_window, h_char, n);
418*5f7ddb14SDimitry Andric   }
Box(chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)419*5f7ddb14SDimitry Andric   void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
420*5f7ddb14SDimitry Andric     ::box(m_window, v_char, h_char);
421*5f7ddb14SDimitry Andric   }
422*5f7ddb14SDimitry Andric 
TitledBox(const char * title,chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)423*5f7ddb14SDimitry Andric   void TitledBox(const char *title, chtype v_char = ACS_VLINE,
424*5f7ddb14SDimitry Andric                  chtype h_char = ACS_HLINE) {
425*5f7ddb14SDimitry Andric     Box(v_char, h_char);
426*5f7ddb14SDimitry Andric     int title_offset = 2;
427*5f7ddb14SDimitry Andric     MoveCursor(title_offset, 0);
428*5f7ddb14SDimitry Andric     PutChar('[');
429*5f7ddb14SDimitry Andric     PutCString(title, GetWidth() - title_offset);
430*5f7ddb14SDimitry Andric     PutChar(']');
431*5f7ddb14SDimitry Andric   }
432*5f7ddb14SDimitry Andric 
Box(const Rect & bounds,chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)433*5f7ddb14SDimitry Andric   void Box(const Rect &bounds, chtype v_char = ACS_VLINE,
434*5f7ddb14SDimitry Andric            chtype h_char = ACS_HLINE) {
435*5f7ddb14SDimitry Andric     MoveCursor(bounds.origin.x, bounds.origin.y);
436*5f7ddb14SDimitry Andric     VerticalLine(bounds.size.height);
437*5f7ddb14SDimitry Andric     HorizontalLine(bounds.size.width);
438*5f7ddb14SDimitry Andric     PutChar(ACS_ULCORNER);
439*5f7ddb14SDimitry Andric 
440*5f7ddb14SDimitry Andric     MoveCursor(bounds.origin.x + bounds.size.width - 1, bounds.origin.y);
441*5f7ddb14SDimitry Andric     VerticalLine(bounds.size.height);
442*5f7ddb14SDimitry Andric     PutChar(ACS_URCORNER);
443*5f7ddb14SDimitry Andric 
444*5f7ddb14SDimitry Andric     MoveCursor(bounds.origin.x, bounds.origin.y + bounds.size.height - 1);
445*5f7ddb14SDimitry Andric     HorizontalLine(bounds.size.width);
446*5f7ddb14SDimitry Andric     PutChar(ACS_LLCORNER);
447*5f7ddb14SDimitry Andric 
448*5f7ddb14SDimitry Andric     MoveCursor(bounds.origin.x + bounds.size.width - 1,
449*5f7ddb14SDimitry Andric                bounds.origin.y + bounds.size.height - 1);
450*5f7ddb14SDimitry Andric     PutChar(ACS_LRCORNER);
451*5f7ddb14SDimitry Andric   }
452*5f7ddb14SDimitry Andric 
TitledBox(const Rect & bounds,const char * title,chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)453*5f7ddb14SDimitry Andric   void TitledBox(const Rect &bounds, const char *title,
454*5f7ddb14SDimitry Andric                  chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
455*5f7ddb14SDimitry Andric     Box(bounds, v_char, h_char);
456*5f7ddb14SDimitry Andric     int title_offset = 2;
457*5f7ddb14SDimitry Andric     MoveCursor(bounds.origin.x + title_offset, bounds.origin.y);
458*5f7ddb14SDimitry Andric     PutChar('[');
459*5f7ddb14SDimitry Andric     PutCString(title, bounds.size.width - title_offset);
460*5f7ddb14SDimitry Andric     PutChar(']');
461af732203SDimitry Andric   }
462af732203SDimitry Andric 
463af732203SDimitry Andric   // Curses doesn't allow direct output of color escape sequences, but that's
464af732203SDimitry Andric   // how we get source lines from the Highligher class. Read the line and
465af732203SDimitry Andric   // convert color escape sequences to curses color attributes. Use
466af732203SDimitry Andric   // first_skip_count to skip leading visible characters. Returns false if all
467af732203SDimitry Andric   // visible characters were skipped due to first_skip_count.
OutputColoredStringTruncated(int right_pad,StringRef string,size_t skip_first_count,bool use_blue_background)468af732203SDimitry Andric   bool OutputColoredStringTruncated(int right_pad, StringRef string,
469af732203SDimitry Andric                                     size_t skip_first_count,
470af732203SDimitry Andric                                     bool use_blue_background) {
471af732203SDimitry Andric     attr_t saved_attr;
472af732203SDimitry Andric     short saved_pair;
473af732203SDimitry Andric     bool result = false;
474af732203SDimitry Andric     wattr_get(m_window, &saved_attr, &saved_pair, nullptr);
475af732203SDimitry Andric     if (use_blue_background)
476af732203SDimitry Andric       ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
477af732203SDimitry Andric     while (!string.empty()) {
478af732203SDimitry Andric       size_t esc_pos = string.find('\x1b');
479af732203SDimitry Andric       if (esc_pos == StringRef::npos) {
480af732203SDimitry Andric         string = string.substr(skip_first_count);
481af732203SDimitry Andric         if (!string.empty()) {
482af732203SDimitry Andric           PutCStringTruncated(right_pad, string.data(), string.size());
483af732203SDimitry Andric           result = true;
484af732203SDimitry Andric         }
485af732203SDimitry Andric         break;
486af732203SDimitry Andric       }
487af732203SDimitry Andric       if (esc_pos > 0) {
488af732203SDimitry Andric         if (skip_first_count > 0) {
489af732203SDimitry Andric           int skip = std::min(esc_pos, skip_first_count);
490af732203SDimitry Andric           string = string.substr(skip);
491af732203SDimitry Andric           skip_first_count -= skip;
492af732203SDimitry Andric           esc_pos -= skip;
493af732203SDimitry Andric         }
494af732203SDimitry Andric         if (esc_pos > 0) {
495af732203SDimitry Andric           PutCStringTruncated(right_pad, string.data(), esc_pos);
496af732203SDimitry Andric           result = true;
497af732203SDimitry Andric           string = string.drop_front(esc_pos);
498af732203SDimitry Andric         }
499af732203SDimitry Andric       }
500af732203SDimitry Andric       bool consumed = string.consume_front("\x1b");
501af732203SDimitry Andric       assert(consumed);
502af732203SDimitry Andric       UNUSED_IF_ASSERT_DISABLED(consumed);
503af732203SDimitry Andric       // This is written to match our Highlighter classes, which seem to
504af732203SDimitry Andric       // generate only foreground color escape sequences. If necessary, this
505af732203SDimitry Andric       // will need to be extended.
506af732203SDimitry Andric       if (!string.consume_front("[")) {
507af732203SDimitry Andric         llvm::errs() << "Missing '[' in color escape sequence.\n";
508af732203SDimitry Andric         continue;
509af732203SDimitry Andric       }
510af732203SDimitry Andric       // Only 8 basic foreground colors and reset, our Highlighter doesn't use
511af732203SDimitry Andric       // anything else.
512af732203SDimitry Andric       int value;
513af732203SDimitry Andric       if (!!string.consumeInteger(10, value) || // Returns false on success.
514af732203SDimitry Andric           !(value == 0 || (value >= 30 && value <= 37))) {
515af732203SDimitry Andric         llvm::errs() << "No valid color code in color escape sequence.\n";
516af732203SDimitry Andric         continue;
517af732203SDimitry Andric       }
518af732203SDimitry Andric       if (!string.consume_front("m")) {
519af732203SDimitry Andric         llvm::errs() << "Missing 'm' in color escape sequence.\n";
520af732203SDimitry Andric         continue;
521af732203SDimitry Andric       }
522af732203SDimitry Andric       if (value == 0) { // Reset.
523af732203SDimitry Andric         wattr_set(m_window, saved_attr, saved_pair, nullptr);
524af732203SDimitry Andric         if (use_blue_background)
525af732203SDimitry Andric           ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
526af732203SDimitry Andric       } else {
527af732203SDimitry Andric         // Mapped directly to first 16 color pairs (black/blue background).
528af732203SDimitry Andric         ::wattron(m_window,
529af732203SDimitry Andric                   COLOR_PAIR(value - 30 + 1 + (use_blue_background ? 8 : 0)));
530af732203SDimitry Andric       }
531af732203SDimitry Andric     }
532af732203SDimitry Andric     wattr_set(m_window, saved_attr, saved_pair, nullptr);
533af732203SDimitry Andric     return result;
534af732203SDimitry Andric   }
535af732203SDimitry Andric 
536*5f7ddb14SDimitry Andric protected:
537*5f7ddb14SDimitry Andric   WINDOW *m_window;
538*5f7ddb14SDimitry Andric };
539*5f7ddb14SDimitry Andric 
540*5f7ddb14SDimitry Andric class Pad : public Surface {
541*5f7ddb14SDimitry Andric public:
Pad(Size size)542*5f7ddb14SDimitry Andric   Pad(Size size) { m_window = ::newpad(size.height, size.width); }
543*5f7ddb14SDimitry Andric 
~Pad()544*5f7ddb14SDimitry Andric   ~Pad() { ::delwin(m_window); }
545*5f7ddb14SDimitry Andric };
546*5f7ddb14SDimitry Andric 
547*5f7ddb14SDimitry Andric class SubPad : public Surface {
548*5f7ddb14SDimitry Andric public:
SubPad(Pad & pad,Rect bounds)549*5f7ddb14SDimitry Andric   SubPad(Pad &pad, Rect bounds) {
550*5f7ddb14SDimitry Andric     m_window = ::subpad(pad.get(), bounds.size.height, bounds.size.width,
551*5f7ddb14SDimitry Andric                         bounds.origin.y, bounds.origin.x);
552*5f7ddb14SDimitry Andric   }
SubPad(SubPad & subpad,Rect bounds)553*5f7ddb14SDimitry Andric   SubPad(SubPad &subpad, Rect bounds) {
554*5f7ddb14SDimitry Andric     m_window = ::subpad(subpad.get(), bounds.size.height, bounds.size.width,
555*5f7ddb14SDimitry Andric                         bounds.origin.y, bounds.origin.x);
556*5f7ddb14SDimitry Andric   }
557*5f7ddb14SDimitry Andric 
~SubPad()558*5f7ddb14SDimitry Andric   ~SubPad() { ::delwin(m_window); }
559*5f7ddb14SDimitry Andric };
560*5f7ddb14SDimitry Andric 
561*5f7ddb14SDimitry Andric class Window : public Surface {
562*5f7ddb14SDimitry Andric public:
Window(const char * name)563*5f7ddb14SDimitry Andric   Window(const char *name)
564*5f7ddb14SDimitry Andric       : m_name(name), m_panel(nullptr), m_parent(nullptr), m_subwindows(),
565*5f7ddb14SDimitry Andric         m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX),
566*5f7ddb14SDimitry Andric         m_prev_active_window_idx(UINT32_MAX), m_delete(false),
567*5f7ddb14SDimitry Andric         m_needs_update(true), m_can_activate(true), m_is_subwin(false) {}
568*5f7ddb14SDimitry Andric 
Window(const char * name,WINDOW * w,bool del=true)569*5f7ddb14SDimitry Andric   Window(const char *name, WINDOW *w, bool del = true)
570*5f7ddb14SDimitry Andric       : m_name(name), m_panel(nullptr), m_parent(nullptr), m_subwindows(),
571*5f7ddb14SDimitry Andric         m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX),
572*5f7ddb14SDimitry Andric         m_prev_active_window_idx(UINT32_MAX), m_delete(del),
573*5f7ddb14SDimitry Andric         m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
574*5f7ddb14SDimitry Andric     if (w)
575*5f7ddb14SDimitry Andric       Reset(w);
576*5f7ddb14SDimitry Andric   }
577*5f7ddb14SDimitry Andric 
Window(const char * name,const Rect & bounds)578*5f7ddb14SDimitry Andric   Window(const char *name, const Rect &bounds)
579*5f7ddb14SDimitry Andric       : m_name(name), m_parent(nullptr), m_subwindows(), m_delegate_sp(),
580*5f7ddb14SDimitry Andric         m_curr_active_window_idx(UINT32_MAX),
581*5f7ddb14SDimitry Andric         m_prev_active_window_idx(UINT32_MAX), m_delete(true),
582*5f7ddb14SDimitry Andric         m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
583*5f7ddb14SDimitry Andric     Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y,
584*5f7ddb14SDimitry Andric                    bounds.origin.y));
585*5f7ddb14SDimitry Andric   }
586*5f7ddb14SDimitry Andric 
~Window()587*5f7ddb14SDimitry Andric   virtual ~Window() {
588*5f7ddb14SDimitry Andric     RemoveSubWindows();
589*5f7ddb14SDimitry Andric     Reset();
590*5f7ddb14SDimitry Andric   }
591*5f7ddb14SDimitry Andric 
Reset(WINDOW * w=nullptr,bool del=true)592*5f7ddb14SDimitry Andric   void Reset(WINDOW *w = nullptr, bool del = true) {
593*5f7ddb14SDimitry Andric     if (m_window == w)
594*5f7ddb14SDimitry Andric       return;
595*5f7ddb14SDimitry Andric 
596*5f7ddb14SDimitry Andric     if (m_panel) {
597*5f7ddb14SDimitry Andric       ::del_panel(m_panel);
598*5f7ddb14SDimitry Andric       m_panel = nullptr;
599*5f7ddb14SDimitry Andric     }
600*5f7ddb14SDimitry Andric     if (m_window && m_delete) {
601*5f7ddb14SDimitry Andric       ::delwin(m_window);
602*5f7ddb14SDimitry Andric       m_window = nullptr;
603*5f7ddb14SDimitry Andric       m_delete = false;
604*5f7ddb14SDimitry Andric     }
605*5f7ddb14SDimitry Andric     if (w) {
606*5f7ddb14SDimitry Andric       m_window = w;
607*5f7ddb14SDimitry Andric       m_panel = ::new_panel(m_window);
608*5f7ddb14SDimitry Andric       m_delete = del;
609*5f7ddb14SDimitry Andric     }
610*5f7ddb14SDimitry Andric   }
611*5f7ddb14SDimitry Andric 
612*5f7ddb14SDimitry Andric   // Get the rectangle in our parent window
GetBounds() const613*5f7ddb14SDimitry Andric   Rect GetBounds() const { return Rect(GetParentOrigin(), GetSize()); }
614*5f7ddb14SDimitry Andric 
GetCenteredRect(int width,int height)615*5f7ddb14SDimitry Andric   Rect GetCenteredRect(int width, int height) {
616*5f7ddb14SDimitry Andric     Size size = GetSize();
617*5f7ddb14SDimitry Andric     width = std::min(size.width, width);
618*5f7ddb14SDimitry Andric     height = std::min(size.height, height);
619*5f7ddb14SDimitry Andric     int x = (size.width - width) / 2;
620*5f7ddb14SDimitry Andric     int y = (size.height - height) / 2;
621*5f7ddb14SDimitry Andric     return Rect(Point(x, y), Size(width, height));
622*5f7ddb14SDimitry Andric   }
623*5f7ddb14SDimitry Andric 
GetChar()624*5f7ddb14SDimitry Andric   int GetChar() { return ::wgetch(m_window); }
GetParentOrigin() const625*5f7ddb14SDimitry Andric   Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); }
GetParentX() const626*5f7ddb14SDimitry Andric   int GetParentX() const { return getparx(m_window); }
GetParentY() const627*5f7ddb14SDimitry Andric   int GetParentY() const { return getpary(m_window); }
MoveWindow(int x,int y)628*5f7ddb14SDimitry Andric   void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); }
Resize(int w,int h)629*5f7ddb14SDimitry Andric   void Resize(int w, int h) { ::wresize(m_window, h, w); }
Resize(const Size & size)630*5f7ddb14SDimitry Andric   void Resize(const Size &size) {
631*5f7ddb14SDimitry Andric     ::wresize(m_window, size.height, size.width);
632*5f7ddb14SDimitry Andric   }
MoveWindow(const Point & origin)633*5f7ddb14SDimitry Andric   void MoveWindow(const Point &origin) {
634*5f7ddb14SDimitry Andric     const bool moving_window = origin != GetParentOrigin();
635*5f7ddb14SDimitry Andric     if (m_is_subwin && moving_window) {
636*5f7ddb14SDimitry Andric       // Can't move subwindows, must delete and re-create
637*5f7ddb14SDimitry Andric       Size size = GetSize();
638*5f7ddb14SDimitry Andric       Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y,
639*5f7ddb14SDimitry Andric                      origin.x),
640*5f7ddb14SDimitry Andric             true);
641*5f7ddb14SDimitry Andric     } else {
642*5f7ddb14SDimitry Andric       ::mvwin(m_window, origin.y, origin.x);
643*5f7ddb14SDimitry Andric     }
644*5f7ddb14SDimitry Andric   }
645*5f7ddb14SDimitry Andric 
SetBounds(const Rect & bounds)646*5f7ddb14SDimitry Andric   void SetBounds(const Rect &bounds) {
647*5f7ddb14SDimitry Andric     const bool moving_window = bounds.origin != GetParentOrigin();
648*5f7ddb14SDimitry Andric     if (m_is_subwin && moving_window) {
649*5f7ddb14SDimitry Andric       // Can't move subwindows, must delete and re-create
650*5f7ddb14SDimitry Andric       Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width,
651*5f7ddb14SDimitry Andric                      bounds.origin.y, bounds.origin.x),
652*5f7ddb14SDimitry Andric             true);
653*5f7ddb14SDimitry Andric     } else {
654*5f7ddb14SDimitry Andric       if (moving_window)
655*5f7ddb14SDimitry Andric         MoveWindow(bounds.origin);
656*5f7ddb14SDimitry Andric       Resize(bounds.size);
657*5f7ddb14SDimitry Andric     }
658*5f7ddb14SDimitry Andric   }
659*5f7ddb14SDimitry Andric 
Touch()660480093f4SDimitry Andric   void Touch() {
661480093f4SDimitry Andric     ::touchwin(m_window);
662480093f4SDimitry Andric     if (m_parent)
663480093f4SDimitry Andric       m_parent->Touch();
664480093f4SDimitry Andric   }
665480093f4SDimitry Andric 
CreateSubWindow(const char * name,const Rect & bounds,bool make_active)666480093f4SDimitry Andric   WindowSP CreateSubWindow(const char *name, const Rect &bounds,
667480093f4SDimitry Andric                            bool make_active) {
668480093f4SDimitry Andric     auto get_window = [this, &bounds]() {
669480093f4SDimitry Andric       return m_window
670480093f4SDimitry Andric                  ? ::subwin(m_window, bounds.size.height, bounds.size.width,
671480093f4SDimitry Andric                             bounds.origin.y, bounds.origin.x)
672480093f4SDimitry Andric                  : ::newwin(bounds.size.height, bounds.size.width,
673480093f4SDimitry Andric                             bounds.origin.y, bounds.origin.x);
674480093f4SDimitry Andric     };
675480093f4SDimitry Andric     WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true);
676480093f4SDimitry Andric     subwindow_sp->m_is_subwin = subwindow_sp.operator bool();
677480093f4SDimitry Andric     subwindow_sp->m_parent = this;
678480093f4SDimitry Andric     if (make_active) {
679480093f4SDimitry Andric       m_prev_active_window_idx = m_curr_active_window_idx;
680480093f4SDimitry Andric       m_curr_active_window_idx = m_subwindows.size();
681480093f4SDimitry Andric     }
682480093f4SDimitry Andric     m_subwindows.push_back(subwindow_sp);
683480093f4SDimitry Andric     ::top_panel(subwindow_sp->m_panel);
684480093f4SDimitry Andric     m_needs_update = true;
685480093f4SDimitry Andric     return subwindow_sp;
686480093f4SDimitry Andric   }
687480093f4SDimitry Andric 
RemoveSubWindow(Window * window)688480093f4SDimitry Andric   bool RemoveSubWindow(Window *window) {
689480093f4SDimitry Andric     Windows::iterator pos, end = m_subwindows.end();
690480093f4SDimitry Andric     size_t i = 0;
691480093f4SDimitry Andric     for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
692480093f4SDimitry Andric       if ((*pos).get() == window) {
693480093f4SDimitry Andric         if (m_prev_active_window_idx == i)
694480093f4SDimitry Andric           m_prev_active_window_idx = UINT32_MAX;
695480093f4SDimitry Andric         else if (m_prev_active_window_idx != UINT32_MAX &&
696480093f4SDimitry Andric                  m_prev_active_window_idx > i)
697480093f4SDimitry Andric           --m_prev_active_window_idx;
698480093f4SDimitry Andric 
699480093f4SDimitry Andric         if (m_curr_active_window_idx == i)
700480093f4SDimitry Andric           m_curr_active_window_idx = UINT32_MAX;
701480093f4SDimitry Andric         else if (m_curr_active_window_idx != UINT32_MAX &&
702480093f4SDimitry Andric                  m_curr_active_window_idx > i)
703480093f4SDimitry Andric           --m_curr_active_window_idx;
704480093f4SDimitry Andric         window->Erase();
705480093f4SDimitry Andric         m_subwindows.erase(pos);
706480093f4SDimitry Andric         m_needs_update = true;
707480093f4SDimitry Andric         if (m_parent)
708480093f4SDimitry Andric           m_parent->Touch();
709480093f4SDimitry Andric         else
710480093f4SDimitry Andric           ::touchwin(stdscr);
711480093f4SDimitry Andric         return true;
712480093f4SDimitry Andric       }
713480093f4SDimitry Andric     }
714480093f4SDimitry Andric     return false;
715480093f4SDimitry Andric   }
716480093f4SDimitry Andric 
FindSubWindow(const char * name)717480093f4SDimitry Andric   WindowSP FindSubWindow(const char *name) {
718480093f4SDimitry Andric     Windows::iterator pos, end = m_subwindows.end();
719480093f4SDimitry Andric     size_t i = 0;
720480093f4SDimitry Andric     for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
721480093f4SDimitry Andric       if ((*pos)->m_name == name)
722480093f4SDimitry Andric         return *pos;
723480093f4SDimitry Andric     }
724480093f4SDimitry Andric     return WindowSP();
725480093f4SDimitry Andric   }
726480093f4SDimitry Andric 
RemoveSubWindows()727480093f4SDimitry Andric   void RemoveSubWindows() {
728480093f4SDimitry Andric     m_curr_active_window_idx = UINT32_MAX;
729480093f4SDimitry Andric     m_prev_active_window_idx = UINT32_MAX;
730480093f4SDimitry Andric     for (Windows::iterator pos = m_subwindows.begin();
731480093f4SDimitry Andric          pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) {
732480093f4SDimitry Andric       (*pos)->Erase();
733480093f4SDimitry Andric     }
734480093f4SDimitry Andric     if (m_parent)
735480093f4SDimitry Andric       m_parent->Touch();
736480093f4SDimitry Andric     else
737480093f4SDimitry Andric       ::touchwin(stdscr);
738480093f4SDimitry Andric   }
739480093f4SDimitry Andric 
740480093f4SDimitry Andric   // Window drawing utilities
DrawTitleBox(const char * title,const char * bottom_message=nullptr)741480093f4SDimitry Andric   void DrawTitleBox(const char *title, const char *bottom_message = nullptr) {
742480093f4SDimitry Andric     attr_t attr = 0;
743480093f4SDimitry Andric     if (IsActive())
744af732203SDimitry Andric       attr = A_BOLD | COLOR_PAIR(BlackOnWhite);
745480093f4SDimitry Andric     else
746480093f4SDimitry Andric       attr = 0;
747480093f4SDimitry Andric     if (attr)
748480093f4SDimitry Andric       AttributeOn(attr);
749480093f4SDimitry Andric 
750480093f4SDimitry Andric     Box();
751480093f4SDimitry Andric     MoveCursor(3, 0);
752480093f4SDimitry Andric 
753480093f4SDimitry Andric     if (title && title[0]) {
754480093f4SDimitry Andric       PutChar('<');
755480093f4SDimitry Andric       PutCString(title);
756480093f4SDimitry Andric       PutChar('>');
757480093f4SDimitry Andric     }
758480093f4SDimitry Andric 
759480093f4SDimitry Andric     if (bottom_message && bottom_message[0]) {
760480093f4SDimitry Andric       int bottom_message_length = strlen(bottom_message);
761480093f4SDimitry Andric       int x = GetWidth() - 3 - (bottom_message_length + 2);
762480093f4SDimitry Andric 
763480093f4SDimitry Andric       if (x > 0) {
764480093f4SDimitry Andric         MoveCursor(x, GetHeight() - 1);
765480093f4SDimitry Andric         PutChar('[');
766480093f4SDimitry Andric         PutCString(bottom_message);
767480093f4SDimitry Andric         PutChar(']');
768480093f4SDimitry Andric       } else {
769480093f4SDimitry Andric         MoveCursor(1, GetHeight() - 1);
770480093f4SDimitry Andric         PutChar('[');
771af732203SDimitry Andric         PutCStringTruncated(1, bottom_message);
772480093f4SDimitry Andric       }
773480093f4SDimitry Andric     }
774480093f4SDimitry Andric     if (attr)
775480093f4SDimitry Andric       AttributeOff(attr);
776480093f4SDimitry Andric   }
777480093f4SDimitry Andric 
Draw(bool force)778480093f4SDimitry Andric   virtual void Draw(bool force) {
779480093f4SDimitry Andric     if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force))
780480093f4SDimitry Andric       return;
781480093f4SDimitry Andric 
782480093f4SDimitry Andric     for (auto &subwindow_sp : m_subwindows)
783480093f4SDimitry Andric       subwindow_sp->Draw(force);
784480093f4SDimitry Andric   }
785480093f4SDimitry Andric 
CreateHelpSubwindow()786480093f4SDimitry Andric   bool CreateHelpSubwindow() {
787480093f4SDimitry Andric     if (m_delegate_sp) {
788480093f4SDimitry Andric       const char *text = m_delegate_sp->WindowDelegateGetHelpText();
789480093f4SDimitry Andric       KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp();
790480093f4SDimitry Andric       if ((text && text[0]) || key_help) {
791480093f4SDimitry Andric         std::unique_ptr<HelpDialogDelegate> help_delegate_up(
792480093f4SDimitry Andric             new HelpDialogDelegate(text, key_help));
793480093f4SDimitry Andric         const size_t num_lines = help_delegate_up->GetNumLines();
794480093f4SDimitry Andric         const size_t max_length = help_delegate_up->GetMaxLineLength();
795480093f4SDimitry Andric         Rect bounds = GetBounds();
796480093f4SDimitry Andric         bounds.Inset(1, 1);
797480093f4SDimitry Andric         if (max_length + 4 < static_cast<size_t>(bounds.size.width)) {
798480093f4SDimitry Andric           bounds.origin.x += (bounds.size.width - max_length + 4) / 2;
799480093f4SDimitry Andric           bounds.size.width = max_length + 4;
800480093f4SDimitry Andric         } else {
801480093f4SDimitry Andric           if (bounds.size.width > 100) {
802480093f4SDimitry Andric             const int inset_w = bounds.size.width / 4;
803480093f4SDimitry Andric             bounds.origin.x += inset_w;
804480093f4SDimitry Andric             bounds.size.width -= 2 * inset_w;
805480093f4SDimitry Andric           }
806480093f4SDimitry Andric         }
807480093f4SDimitry Andric 
808480093f4SDimitry Andric         if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) {
809480093f4SDimitry Andric           bounds.origin.y += (bounds.size.height - num_lines + 2) / 2;
810480093f4SDimitry Andric           bounds.size.height = num_lines + 2;
811480093f4SDimitry Andric         } else {
812480093f4SDimitry Andric           if (bounds.size.height > 100) {
813480093f4SDimitry Andric             const int inset_h = bounds.size.height / 4;
814480093f4SDimitry Andric             bounds.origin.y += inset_h;
815480093f4SDimitry Andric             bounds.size.height -= 2 * inset_h;
816480093f4SDimitry Andric           }
817480093f4SDimitry Andric         }
818480093f4SDimitry Andric         WindowSP help_window_sp;
819480093f4SDimitry Andric         Window *parent_window = GetParent();
820480093f4SDimitry Andric         if (parent_window)
821480093f4SDimitry Andric           help_window_sp = parent_window->CreateSubWindow("Help", bounds, true);
822480093f4SDimitry Andric         else
823480093f4SDimitry Andric           help_window_sp = CreateSubWindow("Help", bounds, true);
824480093f4SDimitry Andric         help_window_sp->SetDelegate(
825480093f4SDimitry Andric             WindowDelegateSP(help_delegate_up.release()));
826480093f4SDimitry Andric         return true;
827480093f4SDimitry Andric       }
828480093f4SDimitry Andric     }
829480093f4SDimitry Andric     return false;
830480093f4SDimitry Andric   }
831480093f4SDimitry Andric 
HandleChar(int key)832480093f4SDimitry Andric   virtual HandleCharResult HandleChar(int key) {
833480093f4SDimitry Andric     // Always check the active window first
834480093f4SDimitry Andric     HandleCharResult result = eKeyNotHandled;
835480093f4SDimitry Andric     WindowSP active_window_sp = GetActiveWindow();
836480093f4SDimitry Andric     if (active_window_sp) {
837480093f4SDimitry Andric       result = active_window_sp->HandleChar(key);
838480093f4SDimitry Andric       if (result != eKeyNotHandled)
839480093f4SDimitry Andric         return result;
840480093f4SDimitry Andric     }
841480093f4SDimitry Andric 
842480093f4SDimitry Andric     if (m_delegate_sp) {
843480093f4SDimitry Andric       result = m_delegate_sp->WindowDelegateHandleChar(*this, key);
844480093f4SDimitry Andric       if (result != eKeyNotHandled)
845480093f4SDimitry Andric         return result;
846480093f4SDimitry Andric     }
847480093f4SDimitry Andric 
848480093f4SDimitry Andric     // Then check for any windows that want any keys that weren't handled. This
849480093f4SDimitry Andric     // is typically only for a menubar. Make a copy of the subwindows in case
850480093f4SDimitry Andric     // any HandleChar() functions muck with the subwindows. If we don't do
851480093f4SDimitry Andric     // this, we can crash when iterating over the subwindows.
852480093f4SDimitry Andric     Windows subwindows(m_subwindows);
853480093f4SDimitry Andric     for (auto subwindow_sp : subwindows) {
854480093f4SDimitry Andric       if (!subwindow_sp->m_can_activate) {
855480093f4SDimitry Andric         HandleCharResult result = subwindow_sp->HandleChar(key);
856480093f4SDimitry Andric         if (result != eKeyNotHandled)
857480093f4SDimitry Andric           return result;
858480093f4SDimitry Andric       }
859480093f4SDimitry Andric     }
860480093f4SDimitry Andric 
861480093f4SDimitry Andric     return eKeyNotHandled;
862480093f4SDimitry Andric   }
863480093f4SDimitry Andric 
GetActiveWindow()864480093f4SDimitry Andric   WindowSP GetActiveWindow() {
865480093f4SDimitry Andric     if (!m_subwindows.empty()) {
866480093f4SDimitry Andric       if (m_curr_active_window_idx >= m_subwindows.size()) {
867480093f4SDimitry Andric         if (m_prev_active_window_idx < m_subwindows.size()) {
868480093f4SDimitry Andric           m_curr_active_window_idx = m_prev_active_window_idx;
869480093f4SDimitry Andric           m_prev_active_window_idx = UINT32_MAX;
870480093f4SDimitry Andric         } else if (IsActive()) {
871480093f4SDimitry Andric           m_prev_active_window_idx = UINT32_MAX;
872480093f4SDimitry Andric           m_curr_active_window_idx = UINT32_MAX;
873480093f4SDimitry Andric 
874480093f4SDimitry Andric           // Find first window that wants to be active if this window is active
875480093f4SDimitry Andric           const size_t num_subwindows = m_subwindows.size();
876480093f4SDimitry Andric           for (size_t i = 0; i < num_subwindows; ++i) {
877480093f4SDimitry Andric             if (m_subwindows[i]->GetCanBeActive()) {
878480093f4SDimitry Andric               m_curr_active_window_idx = i;
879480093f4SDimitry Andric               break;
880480093f4SDimitry Andric             }
881480093f4SDimitry Andric           }
882480093f4SDimitry Andric         }
883480093f4SDimitry Andric       }
884480093f4SDimitry Andric 
885480093f4SDimitry Andric       if (m_curr_active_window_idx < m_subwindows.size())
886480093f4SDimitry Andric         return m_subwindows[m_curr_active_window_idx];
887480093f4SDimitry Andric     }
888480093f4SDimitry Andric     return WindowSP();
889480093f4SDimitry Andric   }
890480093f4SDimitry Andric 
GetCanBeActive() const891480093f4SDimitry Andric   bool GetCanBeActive() const { return m_can_activate; }
892480093f4SDimitry Andric 
SetCanBeActive(bool b)893480093f4SDimitry Andric   void SetCanBeActive(bool b) { m_can_activate = b; }
894480093f4SDimitry Andric 
SetDelegate(const WindowDelegateSP & delegate_sp)895480093f4SDimitry Andric   void SetDelegate(const WindowDelegateSP &delegate_sp) {
896480093f4SDimitry Andric     m_delegate_sp = delegate_sp;
897480093f4SDimitry Andric   }
898480093f4SDimitry Andric 
GetParent() const899480093f4SDimitry Andric   Window *GetParent() const { return m_parent; }
900480093f4SDimitry Andric 
IsActive() const901480093f4SDimitry Andric   bool IsActive() const {
902480093f4SDimitry Andric     if (m_parent)
903480093f4SDimitry Andric       return m_parent->GetActiveWindow().get() == this;
904480093f4SDimitry Andric     else
905480093f4SDimitry Andric       return true; // Top level window is always active
906480093f4SDimitry Andric   }
907480093f4SDimitry Andric 
SelectNextWindowAsActive()908480093f4SDimitry Andric   void SelectNextWindowAsActive() {
909480093f4SDimitry Andric     // Move active focus to next window
910af732203SDimitry Andric     const int num_subwindows = m_subwindows.size();
911af732203SDimitry Andric     int start_idx = 0;
912af732203SDimitry Andric     if (m_curr_active_window_idx != UINT32_MAX) {
913480093f4SDimitry Andric       m_prev_active_window_idx = m_curr_active_window_idx;
914af732203SDimitry Andric       start_idx = m_curr_active_window_idx + 1;
915af732203SDimitry Andric     }
916af732203SDimitry Andric     for (int idx = start_idx; idx < num_subwindows; ++idx) {
917480093f4SDimitry Andric       if (m_subwindows[idx]->GetCanBeActive()) {
918480093f4SDimitry Andric         m_curr_active_window_idx = idx;
919af732203SDimitry Andric         return;
920480093f4SDimitry Andric       }
921480093f4SDimitry Andric     }
922af732203SDimitry Andric     for (int idx = 0; idx < start_idx; ++idx) {
923480093f4SDimitry Andric       if (m_subwindows[idx]->GetCanBeActive()) {
924480093f4SDimitry Andric         m_curr_active_window_idx = idx;
925480093f4SDimitry Andric         break;
926480093f4SDimitry Andric       }
927480093f4SDimitry Andric     }
928480093f4SDimitry Andric   }
929af732203SDimitry Andric 
SelectPreviousWindowAsActive()930af732203SDimitry Andric   void SelectPreviousWindowAsActive() {
931af732203SDimitry Andric     // Move active focus to previous window
932af732203SDimitry Andric     const int num_subwindows = m_subwindows.size();
933af732203SDimitry Andric     int start_idx = num_subwindows - 1;
934af732203SDimitry Andric     if (m_curr_active_window_idx != UINT32_MAX) {
935480093f4SDimitry Andric       m_prev_active_window_idx = m_curr_active_window_idx;
936af732203SDimitry Andric       start_idx = m_curr_active_window_idx - 1;
937af732203SDimitry Andric     }
938af732203SDimitry Andric     for (int idx = start_idx; idx >= 0; --idx) {
939af732203SDimitry Andric       if (m_subwindows[idx]->GetCanBeActive()) {
940af732203SDimitry Andric         m_curr_active_window_idx = idx;
941af732203SDimitry Andric         return;
942af732203SDimitry Andric       }
943af732203SDimitry Andric     }
944af732203SDimitry Andric     for (int idx = num_subwindows - 1; idx > start_idx; --idx) {
945480093f4SDimitry Andric       if (m_subwindows[idx]->GetCanBeActive()) {
946480093f4SDimitry Andric         m_curr_active_window_idx = idx;
947480093f4SDimitry Andric         break;
948480093f4SDimitry Andric       }
949480093f4SDimitry Andric     }
950480093f4SDimitry Andric   }
951480093f4SDimitry Andric 
GetName() const952480093f4SDimitry Andric   const char *GetName() const { return m_name.c_str(); }
953480093f4SDimitry Andric 
954480093f4SDimitry Andric protected:
955480093f4SDimitry Andric   std::string m_name;
956480093f4SDimitry Andric   PANEL *m_panel;
957480093f4SDimitry Andric   Window *m_parent;
958480093f4SDimitry Andric   Windows m_subwindows;
959480093f4SDimitry Andric   WindowDelegateSP m_delegate_sp;
960480093f4SDimitry Andric   uint32_t m_curr_active_window_idx;
961480093f4SDimitry Andric   uint32_t m_prev_active_window_idx;
962480093f4SDimitry Andric   bool m_delete;
963480093f4SDimitry Andric   bool m_needs_update;
964480093f4SDimitry Andric   bool m_can_activate;
965480093f4SDimitry Andric   bool m_is_subwin;
966480093f4SDimitry Andric 
967480093f4SDimitry Andric private:
9685ffd83dbSDimitry Andric   Window(const Window &) = delete;
9695ffd83dbSDimitry Andric   const Window &operator=(const Window &) = delete;
970480093f4SDimitry Andric };
971480093f4SDimitry Andric 
972*5f7ddb14SDimitry Andric class DerivedWindow : public Surface {
973*5f7ddb14SDimitry Andric public:
DerivedWindow(Window & window,Rect bounds)974*5f7ddb14SDimitry Andric   DerivedWindow(Window &window, Rect bounds) {
975*5f7ddb14SDimitry Andric     m_window = ::derwin(window.get(), bounds.size.height, bounds.size.width,
976*5f7ddb14SDimitry Andric                         bounds.origin.y, bounds.origin.x);
977*5f7ddb14SDimitry Andric   }
DerivedWindow(DerivedWindow & derived_window,Rect bounds)978*5f7ddb14SDimitry Andric   DerivedWindow(DerivedWindow &derived_window, Rect bounds) {
979*5f7ddb14SDimitry Andric     m_window = ::derwin(derived_window.get(), bounds.size.height,
980*5f7ddb14SDimitry Andric                         bounds.size.width, bounds.origin.y, bounds.origin.x);
981*5f7ddb14SDimitry Andric   }
982*5f7ddb14SDimitry Andric 
~DerivedWindow()983*5f7ddb14SDimitry Andric   ~DerivedWindow() { ::delwin(m_window); }
984*5f7ddb14SDimitry Andric };
985*5f7ddb14SDimitry Andric 
986*5f7ddb14SDimitry Andric /////////
987*5f7ddb14SDimitry Andric // Forms
988*5f7ddb14SDimitry Andric /////////
989*5f7ddb14SDimitry Andric 
990*5f7ddb14SDimitry Andric // A scroll context defines a vertical region that needs to be visible in a
991*5f7ddb14SDimitry Andric // scrolling area. The region is defined by the index of the start and end lines
992*5f7ddb14SDimitry Andric // of the region. The start and end lines may be equal, in which case, the
993*5f7ddb14SDimitry Andric // region is a single line.
994*5f7ddb14SDimitry Andric struct ScrollContext {
995*5f7ddb14SDimitry Andric   int start;
996*5f7ddb14SDimitry Andric   int end;
997*5f7ddb14SDimitry Andric 
ScrollContextcurses::ScrollContext998*5f7ddb14SDimitry Andric   ScrollContext(int line) : start(line), end(line) {}
ScrollContextcurses::ScrollContext999*5f7ddb14SDimitry Andric   ScrollContext(int _start, int _end) : start(_start), end(_end) {}
1000*5f7ddb14SDimitry Andric 
Offsetcurses::ScrollContext1001*5f7ddb14SDimitry Andric   void Offset(int offset) {
1002*5f7ddb14SDimitry Andric     start += offset;
1003*5f7ddb14SDimitry Andric     end += offset;
1004*5f7ddb14SDimitry Andric   }
1005*5f7ddb14SDimitry Andric };
1006*5f7ddb14SDimitry Andric 
1007*5f7ddb14SDimitry Andric class FieldDelegate {
1008*5f7ddb14SDimitry Andric public:
1009*5f7ddb14SDimitry Andric   virtual ~FieldDelegate() = default;
1010*5f7ddb14SDimitry Andric 
1011*5f7ddb14SDimitry Andric   // Returns the number of lines needed to draw the field. The draw method will
1012*5f7ddb14SDimitry Andric   // be given a surface that have exactly this number of lines.
1013*5f7ddb14SDimitry Andric   virtual int FieldDelegateGetHeight() = 0;
1014*5f7ddb14SDimitry Andric 
1015*5f7ddb14SDimitry Andric   // Returns the scroll context in the local coordinates of the field. By
1016*5f7ddb14SDimitry Andric   // default, the scroll context spans the whole field. Bigger fields with
1017*5f7ddb14SDimitry Andric   // internal navigation should override this method to provide a finer context.
1018*5f7ddb14SDimitry Andric   // Typical override methods would first get the scroll context of the internal
1019*5f7ddb14SDimitry Andric   // element then add the offset of the element in the field.
FieldDelegateGetScrollContext()1020*5f7ddb14SDimitry Andric   virtual ScrollContext FieldDelegateGetScrollContext() {
1021*5f7ddb14SDimitry Andric     return ScrollContext(0, FieldDelegateGetHeight() - 1);
1022*5f7ddb14SDimitry Andric   }
1023*5f7ddb14SDimitry Andric 
1024*5f7ddb14SDimitry Andric   // Draw the field in the given subpad surface. The surface have a height that
1025*5f7ddb14SDimitry Andric   // is equal to the height returned by FieldDelegateGetHeight(). If the field
1026*5f7ddb14SDimitry Andric   // is selected in the form window, then is_selected will be true.
1027*5f7ddb14SDimitry Andric   virtual void FieldDelegateDraw(SubPad &surface, bool is_selected) = 0;
1028*5f7ddb14SDimitry Andric 
1029*5f7ddb14SDimitry Andric   // Handle the key that wasn't handled by the form window or a container field.
FieldDelegateHandleChar(int key)1030*5f7ddb14SDimitry Andric   virtual HandleCharResult FieldDelegateHandleChar(int key) {
1031*5f7ddb14SDimitry Andric     return eKeyNotHandled;
1032*5f7ddb14SDimitry Andric   }
1033*5f7ddb14SDimitry Andric 
1034*5f7ddb14SDimitry Andric   // This is executed once the user exists the field, that is, once the user
1035*5f7ddb14SDimitry Andric   // navigates to the next or the previous field. This is particularly useful to
1036*5f7ddb14SDimitry Andric   // do in-field validation and error setting. Fields with internal navigation
1037*5f7ddb14SDimitry Andric   // should call this method on their fields.
FieldDelegateExitCallback()1038*5f7ddb14SDimitry Andric   virtual void FieldDelegateExitCallback() { return; }
1039*5f7ddb14SDimitry Andric 
1040*5f7ddb14SDimitry Andric   // Fields may have internal navigation, for instance, a List Field have
1041*5f7ddb14SDimitry Andric   // multiple internal elements, which needs to be navigated. To allow for this
1042*5f7ddb14SDimitry Andric   // mechanism, the window shouldn't handle the navigation keys all the time,
1043*5f7ddb14SDimitry Andric   // and instead call the key handing method of the selected field. It should
1044*5f7ddb14SDimitry Andric   // only handle the navigation keys when the field contains a single element or
1045*5f7ddb14SDimitry Andric   // have the last or first element selected depending on if the user is
1046*5f7ddb14SDimitry Andric   // navigating forward or backward. Additionally, once a field is selected in
1047*5f7ddb14SDimitry Andric   // the forward or backward direction, its first or last internal element
1048*5f7ddb14SDimitry Andric   // should be selected. The following methods implements those mechanisms.
1049*5f7ddb14SDimitry Andric 
1050*5f7ddb14SDimitry Andric   // Returns true if the first element in the field is selected or if the field
1051*5f7ddb14SDimitry Andric   // contains a single element.
FieldDelegateOnFirstOrOnlyElement()1052*5f7ddb14SDimitry Andric   virtual bool FieldDelegateOnFirstOrOnlyElement() { return true; }
1053*5f7ddb14SDimitry Andric 
1054*5f7ddb14SDimitry Andric   // Returns true if the last element in the field is selected or if the field
1055*5f7ddb14SDimitry Andric   // contains a single element.
FieldDelegateOnLastOrOnlyElement()1056*5f7ddb14SDimitry Andric   virtual bool FieldDelegateOnLastOrOnlyElement() { return true; }
1057*5f7ddb14SDimitry Andric 
1058*5f7ddb14SDimitry Andric   // Select the first element in the field if multiple elements exists.
FieldDelegateSelectFirstElement()1059*5f7ddb14SDimitry Andric   virtual void FieldDelegateSelectFirstElement() { return; }
1060*5f7ddb14SDimitry Andric 
1061*5f7ddb14SDimitry Andric   // Select the last element in the field if multiple elements exists.
FieldDelegateSelectLastElement()1062*5f7ddb14SDimitry Andric   virtual void FieldDelegateSelectLastElement() { return; }
1063*5f7ddb14SDimitry Andric 
1064*5f7ddb14SDimitry Andric   // Returns true if the field has an error, false otherwise.
FieldDelegateHasError()1065*5f7ddb14SDimitry Andric   virtual bool FieldDelegateHasError() { return false; }
1066*5f7ddb14SDimitry Andric 
FieldDelegateIsVisible()1067*5f7ddb14SDimitry Andric   bool FieldDelegateIsVisible() { return m_is_visible; }
1068*5f7ddb14SDimitry Andric 
FieldDelegateHide()1069*5f7ddb14SDimitry Andric   void FieldDelegateHide() { m_is_visible = false; }
1070*5f7ddb14SDimitry Andric 
FieldDelegateShow()1071*5f7ddb14SDimitry Andric   void FieldDelegateShow() { m_is_visible = true; }
1072*5f7ddb14SDimitry Andric 
1073*5f7ddb14SDimitry Andric protected:
1074*5f7ddb14SDimitry Andric   bool m_is_visible = true;
1075*5f7ddb14SDimitry Andric };
1076*5f7ddb14SDimitry Andric 
1077*5f7ddb14SDimitry Andric typedef std::unique_ptr<FieldDelegate> FieldDelegateUP;
1078*5f7ddb14SDimitry Andric 
1079*5f7ddb14SDimitry Andric class TextFieldDelegate : public FieldDelegate {
1080*5f7ddb14SDimitry Andric public:
TextFieldDelegate(const char * label,const char * content,bool required)1081*5f7ddb14SDimitry Andric   TextFieldDelegate(const char *label, const char *content, bool required)
1082*5f7ddb14SDimitry Andric       : m_label(label), m_required(required), m_cursor_position(0),
1083*5f7ddb14SDimitry Andric         m_first_visibile_char(0) {
1084*5f7ddb14SDimitry Andric     if (content)
1085*5f7ddb14SDimitry Andric       m_content = content;
1086*5f7ddb14SDimitry Andric   }
1087*5f7ddb14SDimitry Andric 
1088*5f7ddb14SDimitry Andric   // Text fields are drawn as titled boxes of a single line, with a possible
1089*5f7ddb14SDimitry Andric   // error messages at the end.
1090*5f7ddb14SDimitry Andric   //
1091*5f7ddb14SDimitry Andric   // __[Label]___________
1092*5f7ddb14SDimitry Andric   // |                  |
1093*5f7ddb14SDimitry Andric   // |__________________|
1094*5f7ddb14SDimitry Andric   // - Error message if it exists.
1095*5f7ddb14SDimitry Andric 
1096*5f7ddb14SDimitry Andric   // The text field has a height of 3 lines. 2 lines for borders and 1 line for
1097*5f7ddb14SDimitry Andric   // the content.
GetFieldHeight()1098*5f7ddb14SDimitry Andric   int GetFieldHeight() { return 3; }
1099*5f7ddb14SDimitry Andric 
1100*5f7ddb14SDimitry Andric   // The text field has a full height of 3 or 4 lines. 3 lines for the actual
1101*5f7ddb14SDimitry Andric   // field and an optional line for an error if it exists.
FieldDelegateGetHeight()1102*5f7ddb14SDimitry Andric   int FieldDelegateGetHeight() override {
1103*5f7ddb14SDimitry Andric     int height = GetFieldHeight();
1104*5f7ddb14SDimitry Andric     if (FieldDelegateHasError())
1105*5f7ddb14SDimitry Andric       height++;
1106*5f7ddb14SDimitry Andric     return height;
1107*5f7ddb14SDimitry Andric   }
1108*5f7ddb14SDimitry Andric 
1109*5f7ddb14SDimitry Andric   // Get the cursor X position in the surface coordinate.
GetCursorXPosition()1110*5f7ddb14SDimitry Andric   int GetCursorXPosition() { return m_cursor_position - m_first_visibile_char; }
1111*5f7ddb14SDimitry Andric 
GetContentLength()1112*5f7ddb14SDimitry Andric   int GetContentLength() { return m_content.length(); }
1113*5f7ddb14SDimitry Andric 
DrawContent(SubPad & surface,bool is_selected)1114*5f7ddb14SDimitry Andric   void DrawContent(SubPad &surface, bool is_selected) {
1115*5f7ddb14SDimitry Andric     surface.MoveCursor(0, 0);
1116*5f7ddb14SDimitry Andric     const char *text = m_content.c_str() + m_first_visibile_char;
1117*5f7ddb14SDimitry Andric     surface.PutCString(text, surface.GetWidth());
1118*5f7ddb14SDimitry Andric     m_last_drawn_content_width = surface.GetWidth();
1119*5f7ddb14SDimitry Andric 
1120*5f7ddb14SDimitry Andric     // Highlight the cursor.
1121*5f7ddb14SDimitry Andric     surface.MoveCursor(GetCursorXPosition(), 0);
1122*5f7ddb14SDimitry Andric     if (is_selected)
1123*5f7ddb14SDimitry Andric       surface.AttributeOn(A_REVERSE);
1124*5f7ddb14SDimitry Andric     if (m_cursor_position == GetContentLength())
1125*5f7ddb14SDimitry Andric       // Cursor is past the last character. Highlight an empty space.
1126*5f7ddb14SDimitry Andric       surface.PutChar(' ');
1127*5f7ddb14SDimitry Andric     else
1128*5f7ddb14SDimitry Andric       surface.PutChar(m_content[m_cursor_position]);
1129*5f7ddb14SDimitry Andric     if (is_selected)
1130*5f7ddb14SDimitry Andric       surface.AttributeOff(A_REVERSE);
1131*5f7ddb14SDimitry Andric   }
1132*5f7ddb14SDimitry Andric 
DrawField(SubPad & surface,bool is_selected)1133*5f7ddb14SDimitry Andric   void DrawField(SubPad &surface, bool is_selected) {
1134*5f7ddb14SDimitry Andric     surface.TitledBox(m_label.c_str());
1135*5f7ddb14SDimitry Andric 
1136*5f7ddb14SDimitry Andric     Rect content_bounds = surface.GetFrame();
1137*5f7ddb14SDimitry Andric     content_bounds.Inset(1, 1);
1138*5f7ddb14SDimitry Andric     SubPad content_surface = SubPad(surface, content_bounds);
1139*5f7ddb14SDimitry Andric 
1140*5f7ddb14SDimitry Andric     DrawContent(content_surface, is_selected);
1141*5f7ddb14SDimitry Andric   }
1142*5f7ddb14SDimitry Andric 
DrawError(SubPad & surface)1143*5f7ddb14SDimitry Andric   void DrawError(SubPad &surface) {
1144*5f7ddb14SDimitry Andric     if (!FieldDelegateHasError())
1145*5f7ddb14SDimitry Andric       return;
1146*5f7ddb14SDimitry Andric     surface.MoveCursor(0, 0);
1147*5f7ddb14SDimitry Andric     surface.AttributeOn(COLOR_PAIR(RedOnBlack));
1148*5f7ddb14SDimitry Andric     surface.PutChar(ACS_DIAMOND);
1149*5f7ddb14SDimitry Andric     surface.PutChar(' ');
1150*5f7ddb14SDimitry Andric     surface.PutCStringTruncated(1, GetError().c_str());
1151*5f7ddb14SDimitry Andric     surface.AttributeOff(COLOR_PAIR(RedOnBlack));
1152*5f7ddb14SDimitry Andric   }
1153*5f7ddb14SDimitry Andric 
FieldDelegateDraw(SubPad & surface,bool is_selected)1154*5f7ddb14SDimitry Andric   void FieldDelegateDraw(SubPad &surface, bool is_selected) override {
1155*5f7ddb14SDimitry Andric     Rect frame = surface.GetFrame();
1156*5f7ddb14SDimitry Andric     Rect field_bounds, error_bounds;
1157*5f7ddb14SDimitry Andric     frame.HorizontalSplit(GetFieldHeight(), field_bounds, error_bounds);
1158*5f7ddb14SDimitry Andric     SubPad field_surface = SubPad(surface, field_bounds);
1159*5f7ddb14SDimitry Andric     SubPad error_surface = SubPad(surface, error_bounds);
1160*5f7ddb14SDimitry Andric 
1161*5f7ddb14SDimitry Andric     DrawField(field_surface, is_selected);
1162*5f7ddb14SDimitry Andric     DrawError(error_surface);
1163*5f7ddb14SDimitry Andric   }
1164*5f7ddb14SDimitry Andric 
1165*5f7ddb14SDimitry Andric   // The cursor is allowed to move one character past the string.
1166*5f7ddb14SDimitry Andric   // m_cursor_position is in range [0, GetContentLength()].
MoveCursorRight()1167*5f7ddb14SDimitry Andric   void MoveCursorRight() {
1168*5f7ddb14SDimitry Andric     if (m_cursor_position < GetContentLength())
1169*5f7ddb14SDimitry Andric       m_cursor_position++;
1170*5f7ddb14SDimitry Andric   }
1171*5f7ddb14SDimitry Andric 
MoveCursorLeft()1172*5f7ddb14SDimitry Andric   void MoveCursorLeft() {
1173*5f7ddb14SDimitry Andric     if (m_cursor_position > 0)
1174*5f7ddb14SDimitry Andric       m_cursor_position--;
1175*5f7ddb14SDimitry Andric   }
1176*5f7ddb14SDimitry Andric 
1177*5f7ddb14SDimitry Andric   // If the cursor moved past the last visible character, scroll right by one
1178*5f7ddb14SDimitry Andric   // character.
ScrollRightIfNeeded()1179*5f7ddb14SDimitry Andric   void ScrollRightIfNeeded() {
1180*5f7ddb14SDimitry Andric     if (m_cursor_position - m_first_visibile_char == m_last_drawn_content_width)
1181*5f7ddb14SDimitry Andric       m_first_visibile_char++;
1182*5f7ddb14SDimitry Andric   }
1183*5f7ddb14SDimitry Andric 
ScrollLeft()1184*5f7ddb14SDimitry Andric   void ScrollLeft() {
1185*5f7ddb14SDimitry Andric     if (m_first_visibile_char > 0)
1186*5f7ddb14SDimitry Andric       m_first_visibile_char--;
1187*5f7ddb14SDimitry Andric   }
1188*5f7ddb14SDimitry Andric 
1189*5f7ddb14SDimitry Andric   // If the cursor moved past the first visible character, scroll left by one
1190*5f7ddb14SDimitry Andric   // character.
ScrollLeftIfNeeded()1191*5f7ddb14SDimitry Andric   void ScrollLeftIfNeeded() {
1192*5f7ddb14SDimitry Andric     if (m_cursor_position < m_first_visibile_char)
1193*5f7ddb14SDimitry Andric       m_first_visibile_char--;
1194*5f7ddb14SDimitry Andric   }
1195*5f7ddb14SDimitry Andric 
1196*5f7ddb14SDimitry Andric   // Insert a character at the current cursor position, advance the cursor
1197*5f7ddb14SDimitry Andric   // position, and make sure to scroll right if needed.
InsertChar(char character)1198*5f7ddb14SDimitry Andric   void InsertChar(char character) {
1199*5f7ddb14SDimitry Andric     m_content.insert(m_cursor_position, 1, character);
1200*5f7ddb14SDimitry Andric     m_cursor_position++;
1201*5f7ddb14SDimitry Andric     ScrollRightIfNeeded();
1202*5f7ddb14SDimitry Andric   }
1203*5f7ddb14SDimitry Andric 
1204*5f7ddb14SDimitry Andric   // Remove the character before the cursor position, retreat the cursor
1205*5f7ddb14SDimitry Andric   // position, and make sure to scroll left if needed.
RemoveChar()1206*5f7ddb14SDimitry Andric   void RemoveChar() {
1207*5f7ddb14SDimitry Andric     if (m_cursor_position == 0)
1208*5f7ddb14SDimitry Andric       return;
1209*5f7ddb14SDimitry Andric 
1210*5f7ddb14SDimitry Andric     m_content.erase(m_cursor_position - 1, 1);
1211*5f7ddb14SDimitry Andric     m_cursor_position--;
1212*5f7ddb14SDimitry Andric     ScrollLeft();
1213*5f7ddb14SDimitry Andric   }
1214*5f7ddb14SDimitry Andric 
1215*5f7ddb14SDimitry Andric   // True if the key represents a char that can be inserted in the field
1216*5f7ddb14SDimitry Andric   // content, false otherwise.
IsAcceptableChar(int key)1217*5f7ddb14SDimitry Andric   virtual bool IsAcceptableChar(int key) { return isprint(key); }
1218*5f7ddb14SDimitry Andric 
FieldDelegateHandleChar(int key)1219*5f7ddb14SDimitry Andric   HandleCharResult FieldDelegateHandleChar(int key) override {
1220*5f7ddb14SDimitry Andric     if (IsAcceptableChar(key)) {
1221*5f7ddb14SDimitry Andric       ClearError();
1222*5f7ddb14SDimitry Andric       InsertChar((char)key);
1223*5f7ddb14SDimitry Andric       return eKeyHandled;
1224*5f7ddb14SDimitry Andric     }
1225*5f7ddb14SDimitry Andric 
1226*5f7ddb14SDimitry Andric     switch (key) {
1227*5f7ddb14SDimitry Andric     case KEY_RIGHT:
1228*5f7ddb14SDimitry Andric       MoveCursorRight();
1229*5f7ddb14SDimitry Andric       ScrollRightIfNeeded();
1230*5f7ddb14SDimitry Andric       return eKeyHandled;
1231*5f7ddb14SDimitry Andric     case KEY_LEFT:
1232*5f7ddb14SDimitry Andric       MoveCursorLeft();
1233*5f7ddb14SDimitry Andric       ScrollLeftIfNeeded();
1234*5f7ddb14SDimitry Andric       return eKeyHandled;
1235*5f7ddb14SDimitry Andric     case KEY_BACKSPACE:
1236*5f7ddb14SDimitry Andric       ClearError();
1237*5f7ddb14SDimitry Andric       RemoveChar();
1238*5f7ddb14SDimitry Andric       return eKeyHandled;
1239*5f7ddb14SDimitry Andric     default:
1240*5f7ddb14SDimitry Andric       break;
1241*5f7ddb14SDimitry Andric     }
1242*5f7ddb14SDimitry Andric     return eKeyNotHandled;
1243*5f7ddb14SDimitry Andric   }
1244*5f7ddb14SDimitry Andric 
FieldDelegateHasError()1245*5f7ddb14SDimitry Andric   bool FieldDelegateHasError() override { return !m_error.empty(); }
1246*5f7ddb14SDimitry Andric 
FieldDelegateExitCallback()1247*5f7ddb14SDimitry Andric   void FieldDelegateExitCallback() override {
1248*5f7ddb14SDimitry Andric     if (!IsSpecified() && m_required)
1249*5f7ddb14SDimitry Andric       SetError("This field is required!");
1250*5f7ddb14SDimitry Andric   }
1251*5f7ddb14SDimitry Andric 
IsSpecified()1252*5f7ddb14SDimitry Andric   bool IsSpecified() { return !m_content.empty(); }
1253*5f7ddb14SDimitry Andric 
ClearError()1254*5f7ddb14SDimitry Andric   void ClearError() { m_error.clear(); }
1255*5f7ddb14SDimitry Andric 
GetError()1256*5f7ddb14SDimitry Andric   const std::string &GetError() { return m_error; }
1257*5f7ddb14SDimitry Andric 
SetError(const char * error)1258*5f7ddb14SDimitry Andric   void SetError(const char *error) { m_error = error; }
1259*5f7ddb14SDimitry Andric 
GetText()1260*5f7ddb14SDimitry Andric   const std::string &GetText() { return m_content; }
1261*5f7ddb14SDimitry Andric 
1262*5f7ddb14SDimitry Andric protected:
1263*5f7ddb14SDimitry Andric   std::string m_label;
1264*5f7ddb14SDimitry Andric   bool m_required;
1265*5f7ddb14SDimitry Andric   // The position of the top left corner character of the border.
1266*5f7ddb14SDimitry Andric   std::string m_content;
1267*5f7ddb14SDimitry Andric   // The cursor position in the content string itself. Can be in the range
1268*5f7ddb14SDimitry Andric   // [0, GetContentLength()].
1269*5f7ddb14SDimitry Andric   int m_cursor_position;
1270*5f7ddb14SDimitry Andric   // The index of the first visible character in the content.
1271*5f7ddb14SDimitry Andric   int m_first_visibile_char;
1272*5f7ddb14SDimitry Andric   // The width of the fields content that was last drawn. Width can change, so
1273*5f7ddb14SDimitry Andric   // this is used to determine if scrolling is needed dynamically.
1274*5f7ddb14SDimitry Andric   int m_last_drawn_content_width;
1275*5f7ddb14SDimitry Andric   // Optional error message. If empty, field is considered to have no error.
1276*5f7ddb14SDimitry Andric   std::string m_error;
1277*5f7ddb14SDimitry Andric };
1278*5f7ddb14SDimitry Andric 
1279*5f7ddb14SDimitry Andric class IntegerFieldDelegate : public TextFieldDelegate {
1280*5f7ddb14SDimitry Andric public:
IntegerFieldDelegate(const char * label,int content,bool required)1281*5f7ddb14SDimitry Andric   IntegerFieldDelegate(const char *label, int content, bool required)
1282*5f7ddb14SDimitry Andric       : TextFieldDelegate(label, std::to_string(content).c_str(), required) {}
1283*5f7ddb14SDimitry Andric 
1284*5f7ddb14SDimitry Andric   // Only accept digits.
IsAcceptableChar(int key)1285*5f7ddb14SDimitry Andric   bool IsAcceptableChar(int key) override { return isdigit(key); }
1286*5f7ddb14SDimitry Andric 
1287*5f7ddb14SDimitry Andric   // Returns the integer content of the field.
GetInteger()1288*5f7ddb14SDimitry Andric   int GetInteger() { return std::stoi(m_content); }
1289*5f7ddb14SDimitry Andric };
1290*5f7ddb14SDimitry Andric 
1291*5f7ddb14SDimitry Andric class FileFieldDelegate : public TextFieldDelegate {
1292*5f7ddb14SDimitry Andric public:
FileFieldDelegate(const char * label,const char * content,bool need_to_exist,bool required)1293*5f7ddb14SDimitry Andric   FileFieldDelegate(const char *label, const char *content, bool need_to_exist,
1294*5f7ddb14SDimitry Andric                     bool required)
1295*5f7ddb14SDimitry Andric       : TextFieldDelegate(label, content, required),
1296*5f7ddb14SDimitry Andric         m_need_to_exist(need_to_exist) {}
1297*5f7ddb14SDimitry Andric 
FieldDelegateExitCallback()1298*5f7ddb14SDimitry Andric   void FieldDelegateExitCallback() override {
1299*5f7ddb14SDimitry Andric     TextFieldDelegate::FieldDelegateExitCallback();
1300*5f7ddb14SDimitry Andric     if (!IsSpecified())
1301*5f7ddb14SDimitry Andric       return;
1302*5f7ddb14SDimitry Andric 
1303*5f7ddb14SDimitry Andric     if (!m_need_to_exist)
1304*5f7ddb14SDimitry Andric       return;
1305*5f7ddb14SDimitry Andric 
1306*5f7ddb14SDimitry Andric     FileSpec file = GetResolvedFileSpec();
1307*5f7ddb14SDimitry Andric     if (!FileSystem::Instance().Exists(file)) {
1308*5f7ddb14SDimitry Andric       SetError("File doesn't exist!");
1309*5f7ddb14SDimitry Andric       return;
1310*5f7ddb14SDimitry Andric     }
1311*5f7ddb14SDimitry Andric     if (FileSystem::Instance().IsDirectory(file)) {
1312*5f7ddb14SDimitry Andric       SetError("Not a file!");
1313*5f7ddb14SDimitry Andric       return;
1314*5f7ddb14SDimitry Andric     }
1315*5f7ddb14SDimitry Andric   }
1316*5f7ddb14SDimitry Andric 
GetFileSpec()1317*5f7ddb14SDimitry Andric   FileSpec GetFileSpec() {
1318*5f7ddb14SDimitry Andric     FileSpec file_spec(GetPath());
1319*5f7ddb14SDimitry Andric     return file_spec;
1320*5f7ddb14SDimitry Andric   }
1321*5f7ddb14SDimitry Andric 
GetResolvedFileSpec()1322*5f7ddb14SDimitry Andric   FileSpec GetResolvedFileSpec() {
1323*5f7ddb14SDimitry Andric     FileSpec file_spec(GetPath());
1324*5f7ddb14SDimitry Andric     FileSystem::Instance().Resolve(file_spec);
1325*5f7ddb14SDimitry Andric     return file_spec;
1326*5f7ddb14SDimitry Andric   }
1327*5f7ddb14SDimitry Andric 
GetPath()1328*5f7ddb14SDimitry Andric   const std::string &GetPath() { return m_content; }
1329*5f7ddb14SDimitry Andric 
1330*5f7ddb14SDimitry Andric protected:
1331*5f7ddb14SDimitry Andric   bool m_need_to_exist;
1332*5f7ddb14SDimitry Andric };
1333*5f7ddb14SDimitry Andric 
1334*5f7ddb14SDimitry Andric class DirectoryFieldDelegate : public TextFieldDelegate {
1335*5f7ddb14SDimitry Andric public:
DirectoryFieldDelegate(const char * label,const char * content,bool need_to_exist,bool required)1336*5f7ddb14SDimitry Andric   DirectoryFieldDelegate(const char *label, const char *content,
1337*5f7ddb14SDimitry Andric                          bool need_to_exist, bool required)
1338*5f7ddb14SDimitry Andric       : TextFieldDelegate(label, content, required),
1339*5f7ddb14SDimitry Andric         m_need_to_exist(need_to_exist) {}
1340*5f7ddb14SDimitry Andric 
FieldDelegateExitCallback()1341*5f7ddb14SDimitry Andric   void FieldDelegateExitCallback() override {
1342*5f7ddb14SDimitry Andric     TextFieldDelegate::FieldDelegateExitCallback();
1343*5f7ddb14SDimitry Andric     if (!IsSpecified())
1344*5f7ddb14SDimitry Andric       return;
1345*5f7ddb14SDimitry Andric 
1346*5f7ddb14SDimitry Andric     if (!m_need_to_exist)
1347*5f7ddb14SDimitry Andric       return;
1348*5f7ddb14SDimitry Andric 
1349*5f7ddb14SDimitry Andric     FileSpec file = GetResolvedFileSpec();
1350*5f7ddb14SDimitry Andric     if (!FileSystem::Instance().Exists(file)) {
1351*5f7ddb14SDimitry Andric       SetError("Directory doesn't exist!");
1352*5f7ddb14SDimitry Andric       return;
1353*5f7ddb14SDimitry Andric     }
1354*5f7ddb14SDimitry Andric     if (!FileSystem::Instance().IsDirectory(file)) {
1355*5f7ddb14SDimitry Andric       SetError("Not a directory!");
1356*5f7ddb14SDimitry Andric       return;
1357*5f7ddb14SDimitry Andric     }
1358*5f7ddb14SDimitry Andric   }
1359*5f7ddb14SDimitry Andric 
GetFileSpec()1360*5f7ddb14SDimitry Andric   FileSpec GetFileSpec() {
1361*5f7ddb14SDimitry Andric     FileSpec file_spec(GetPath());
1362*5f7ddb14SDimitry Andric     return file_spec;
1363*5f7ddb14SDimitry Andric   }
1364*5f7ddb14SDimitry Andric 
GetResolvedFileSpec()1365*5f7ddb14SDimitry Andric   FileSpec GetResolvedFileSpec() {
1366*5f7ddb14SDimitry Andric     FileSpec file_spec(GetPath());
1367*5f7ddb14SDimitry Andric     FileSystem::Instance().Resolve(file_spec);
1368*5f7ddb14SDimitry Andric     return file_spec;
1369*5f7ddb14SDimitry Andric   }
1370*5f7ddb14SDimitry Andric 
GetPath()1371*5f7ddb14SDimitry Andric   const std::string &GetPath() { return m_content; }
1372*5f7ddb14SDimitry Andric 
1373*5f7ddb14SDimitry Andric protected:
1374*5f7ddb14SDimitry Andric   bool m_need_to_exist;
1375*5f7ddb14SDimitry Andric };
1376*5f7ddb14SDimitry Andric 
1377*5f7ddb14SDimitry Andric class ArchFieldDelegate : public TextFieldDelegate {
1378*5f7ddb14SDimitry Andric public:
ArchFieldDelegate(const char * label,const char * content,bool required)1379*5f7ddb14SDimitry Andric   ArchFieldDelegate(const char *label, const char *content, bool required)
1380*5f7ddb14SDimitry Andric       : TextFieldDelegate(label, content, required) {}
1381*5f7ddb14SDimitry Andric 
FieldDelegateExitCallback()1382*5f7ddb14SDimitry Andric   void FieldDelegateExitCallback() override {
1383*5f7ddb14SDimitry Andric     TextFieldDelegate::FieldDelegateExitCallback();
1384*5f7ddb14SDimitry Andric     if (!IsSpecified())
1385*5f7ddb14SDimitry Andric       return;
1386*5f7ddb14SDimitry Andric 
1387*5f7ddb14SDimitry Andric     if (!GetArchSpec().IsValid())
1388*5f7ddb14SDimitry Andric       SetError("Not a valid arch!");
1389*5f7ddb14SDimitry Andric   }
1390*5f7ddb14SDimitry Andric 
GetArchString()1391*5f7ddb14SDimitry Andric   const std::string &GetArchString() { return m_content; }
1392*5f7ddb14SDimitry Andric 
GetArchSpec()1393*5f7ddb14SDimitry Andric   ArchSpec GetArchSpec() { return ArchSpec(GetArchString()); }
1394*5f7ddb14SDimitry Andric };
1395*5f7ddb14SDimitry Andric 
1396*5f7ddb14SDimitry Andric class BooleanFieldDelegate : public FieldDelegate {
1397*5f7ddb14SDimitry Andric public:
BooleanFieldDelegate(const char * label,bool content)1398*5f7ddb14SDimitry Andric   BooleanFieldDelegate(const char *label, bool content)
1399*5f7ddb14SDimitry Andric       : m_label(label), m_content(content) {}
1400*5f7ddb14SDimitry Andric 
1401*5f7ddb14SDimitry Andric   // Boolean fields are drawn as checkboxes.
1402*5f7ddb14SDimitry Andric   //
1403*5f7ddb14SDimitry Andric   // [X] Label  or [ ] Label
1404*5f7ddb14SDimitry Andric 
1405*5f7ddb14SDimitry Andric   // Boolean fields are have a single line.
FieldDelegateGetHeight()1406*5f7ddb14SDimitry Andric   int FieldDelegateGetHeight() override { return 1; }
1407*5f7ddb14SDimitry Andric 
FieldDelegateDraw(SubPad & surface,bool is_selected)1408*5f7ddb14SDimitry Andric   void FieldDelegateDraw(SubPad &surface, bool is_selected) override {
1409*5f7ddb14SDimitry Andric     surface.MoveCursor(0, 0);
1410*5f7ddb14SDimitry Andric     surface.PutChar('[');
1411*5f7ddb14SDimitry Andric     if (is_selected)
1412*5f7ddb14SDimitry Andric       surface.AttributeOn(A_REVERSE);
1413*5f7ddb14SDimitry Andric     surface.PutChar(m_content ? ACS_DIAMOND : ' ');
1414*5f7ddb14SDimitry Andric     if (is_selected)
1415*5f7ddb14SDimitry Andric       surface.AttributeOff(A_REVERSE);
1416*5f7ddb14SDimitry Andric     surface.PutChar(']');
1417*5f7ddb14SDimitry Andric     surface.PutChar(' ');
1418*5f7ddb14SDimitry Andric     surface.PutCString(m_label.c_str());
1419*5f7ddb14SDimitry Andric   }
1420*5f7ddb14SDimitry Andric 
ToggleContent()1421*5f7ddb14SDimitry Andric   void ToggleContent() { m_content = !m_content; }
1422*5f7ddb14SDimitry Andric 
SetContentToTrue()1423*5f7ddb14SDimitry Andric   void SetContentToTrue() { m_content = true; }
1424*5f7ddb14SDimitry Andric 
SetContentToFalse()1425*5f7ddb14SDimitry Andric   void SetContentToFalse() { m_content = false; }
1426*5f7ddb14SDimitry Andric 
FieldDelegateHandleChar(int key)1427*5f7ddb14SDimitry Andric   HandleCharResult FieldDelegateHandleChar(int key) override {
1428*5f7ddb14SDimitry Andric     switch (key) {
1429*5f7ddb14SDimitry Andric     case 't':
1430*5f7ddb14SDimitry Andric     case '1':
1431*5f7ddb14SDimitry Andric       SetContentToTrue();
1432*5f7ddb14SDimitry Andric       return eKeyHandled;
1433*5f7ddb14SDimitry Andric     case 'f':
1434*5f7ddb14SDimitry Andric     case '0':
1435*5f7ddb14SDimitry Andric       SetContentToFalse();
1436*5f7ddb14SDimitry Andric       return eKeyHandled;
1437*5f7ddb14SDimitry Andric     case ' ':
1438*5f7ddb14SDimitry Andric     case '\r':
1439*5f7ddb14SDimitry Andric     case '\n':
1440*5f7ddb14SDimitry Andric     case KEY_ENTER:
1441*5f7ddb14SDimitry Andric       ToggleContent();
1442*5f7ddb14SDimitry Andric       return eKeyHandled;
1443*5f7ddb14SDimitry Andric     default:
1444*5f7ddb14SDimitry Andric       break;
1445*5f7ddb14SDimitry Andric     }
1446*5f7ddb14SDimitry Andric     return eKeyNotHandled;
1447*5f7ddb14SDimitry Andric   }
1448*5f7ddb14SDimitry Andric 
1449*5f7ddb14SDimitry Andric   // Returns the boolean content of the field.
GetBoolean()1450*5f7ddb14SDimitry Andric   bool GetBoolean() { return m_content; }
1451*5f7ddb14SDimitry Andric 
1452*5f7ddb14SDimitry Andric protected:
1453*5f7ddb14SDimitry Andric   std::string m_label;
1454*5f7ddb14SDimitry Andric   bool m_content;
1455*5f7ddb14SDimitry Andric };
1456*5f7ddb14SDimitry Andric 
1457*5f7ddb14SDimitry Andric class ChoicesFieldDelegate : public FieldDelegate {
1458*5f7ddb14SDimitry Andric public:
ChoicesFieldDelegate(const char * label,int number_of_visible_choices,std::vector<std::string> choices)1459*5f7ddb14SDimitry Andric   ChoicesFieldDelegate(const char *label, int number_of_visible_choices,
1460*5f7ddb14SDimitry Andric                        std::vector<std::string> choices)
1461*5f7ddb14SDimitry Andric       : m_label(label), m_number_of_visible_choices(number_of_visible_choices),
1462*5f7ddb14SDimitry Andric         m_choices(choices), m_choice(0), m_first_visibile_choice(0) {}
1463*5f7ddb14SDimitry Andric 
1464*5f7ddb14SDimitry Andric   // Choices fields are drawn as titles boxses of a number of visible choices.
1465*5f7ddb14SDimitry Andric   // The rest of the choices become visible as the user scroll. The selected
1466*5f7ddb14SDimitry Andric   // choice is denoted by a diamond as the first character.
1467*5f7ddb14SDimitry Andric   //
1468*5f7ddb14SDimitry Andric   // __[Label]___________
1469*5f7ddb14SDimitry Andric   // |-Choice 1         |
1470*5f7ddb14SDimitry Andric   // | Choice 2         |
1471*5f7ddb14SDimitry Andric   // | Choice 3         |
1472*5f7ddb14SDimitry Andric   // |__________________|
1473*5f7ddb14SDimitry Andric 
1474*5f7ddb14SDimitry Andric   // Choices field have two border characters plus the number of visible
1475*5f7ddb14SDimitry Andric   // choices.
FieldDelegateGetHeight()1476*5f7ddb14SDimitry Andric   int FieldDelegateGetHeight() override {
1477*5f7ddb14SDimitry Andric     return m_number_of_visible_choices + 2;
1478*5f7ddb14SDimitry Andric   }
1479*5f7ddb14SDimitry Andric 
GetNumberOfChoices()1480*5f7ddb14SDimitry Andric   int GetNumberOfChoices() { return m_choices.size(); }
1481*5f7ddb14SDimitry Andric 
1482*5f7ddb14SDimitry Andric   // Get the index of the last visible choice.
GetLastVisibleChoice()1483*5f7ddb14SDimitry Andric   int GetLastVisibleChoice() {
1484*5f7ddb14SDimitry Andric     int index = m_first_visibile_choice + m_number_of_visible_choices;
1485*5f7ddb14SDimitry Andric     return std::min(index, GetNumberOfChoices()) - 1;
1486*5f7ddb14SDimitry Andric   }
1487*5f7ddb14SDimitry Andric 
DrawContent(SubPad & surface,bool is_selected)1488*5f7ddb14SDimitry Andric   void DrawContent(SubPad &surface, bool is_selected) {
1489*5f7ddb14SDimitry Andric     int choices_to_draw = GetLastVisibleChoice() - m_first_visibile_choice + 1;
1490*5f7ddb14SDimitry Andric     for (int i = 0; i < choices_to_draw; i++) {
1491*5f7ddb14SDimitry Andric       surface.MoveCursor(0, i);
1492*5f7ddb14SDimitry Andric       int current_choice = m_first_visibile_choice + i;
1493*5f7ddb14SDimitry Andric       const char *text = m_choices[current_choice].c_str();
1494*5f7ddb14SDimitry Andric       bool highlight = is_selected && current_choice == m_choice;
1495*5f7ddb14SDimitry Andric       if (highlight)
1496*5f7ddb14SDimitry Andric         surface.AttributeOn(A_REVERSE);
1497*5f7ddb14SDimitry Andric       surface.PutChar(current_choice == m_choice ? ACS_DIAMOND : ' ');
1498*5f7ddb14SDimitry Andric       surface.PutCString(text);
1499*5f7ddb14SDimitry Andric       if (highlight)
1500*5f7ddb14SDimitry Andric         surface.AttributeOff(A_REVERSE);
1501*5f7ddb14SDimitry Andric     }
1502*5f7ddb14SDimitry Andric   }
1503*5f7ddb14SDimitry Andric 
FieldDelegateDraw(SubPad & surface,bool is_selected)1504*5f7ddb14SDimitry Andric   void FieldDelegateDraw(SubPad &surface, bool is_selected) override {
1505*5f7ddb14SDimitry Andric     UpdateScrolling();
1506*5f7ddb14SDimitry Andric 
1507*5f7ddb14SDimitry Andric     surface.TitledBox(m_label.c_str());
1508*5f7ddb14SDimitry Andric 
1509*5f7ddb14SDimitry Andric     Rect content_bounds = surface.GetFrame();
1510*5f7ddb14SDimitry Andric     content_bounds.Inset(1, 1);
1511*5f7ddb14SDimitry Andric     SubPad content_surface = SubPad(surface, content_bounds);
1512*5f7ddb14SDimitry Andric 
1513*5f7ddb14SDimitry Andric     DrawContent(content_surface, is_selected);
1514*5f7ddb14SDimitry Andric   }
1515*5f7ddb14SDimitry Andric 
SelectPrevious()1516*5f7ddb14SDimitry Andric   void SelectPrevious() {
1517*5f7ddb14SDimitry Andric     if (m_choice > 0)
1518*5f7ddb14SDimitry Andric       m_choice--;
1519*5f7ddb14SDimitry Andric   }
1520*5f7ddb14SDimitry Andric 
SelectNext()1521*5f7ddb14SDimitry Andric   void SelectNext() {
1522*5f7ddb14SDimitry Andric     if (m_choice < GetNumberOfChoices() - 1)
1523*5f7ddb14SDimitry Andric       m_choice++;
1524*5f7ddb14SDimitry Andric   }
1525*5f7ddb14SDimitry Andric 
UpdateScrolling()1526*5f7ddb14SDimitry Andric   void UpdateScrolling() {
1527*5f7ddb14SDimitry Andric     if (m_choice > GetLastVisibleChoice()) {
1528*5f7ddb14SDimitry Andric       m_first_visibile_choice = m_choice - (m_number_of_visible_choices - 1);
1529*5f7ddb14SDimitry Andric       return;
1530*5f7ddb14SDimitry Andric     }
1531*5f7ddb14SDimitry Andric 
1532*5f7ddb14SDimitry Andric     if (m_choice < m_first_visibile_choice)
1533*5f7ddb14SDimitry Andric       m_first_visibile_choice = m_choice;
1534*5f7ddb14SDimitry Andric   }
1535*5f7ddb14SDimitry Andric 
FieldDelegateHandleChar(int key)1536*5f7ddb14SDimitry Andric   HandleCharResult FieldDelegateHandleChar(int key) override {
1537*5f7ddb14SDimitry Andric     switch (key) {
1538*5f7ddb14SDimitry Andric     case KEY_UP:
1539*5f7ddb14SDimitry Andric       SelectPrevious();
1540*5f7ddb14SDimitry Andric       return eKeyHandled;
1541*5f7ddb14SDimitry Andric     case KEY_DOWN:
1542*5f7ddb14SDimitry Andric       SelectNext();
1543*5f7ddb14SDimitry Andric       return eKeyHandled;
1544*5f7ddb14SDimitry Andric     default:
1545*5f7ddb14SDimitry Andric       break;
1546*5f7ddb14SDimitry Andric     }
1547*5f7ddb14SDimitry Andric     return eKeyNotHandled;
1548*5f7ddb14SDimitry Andric   }
1549*5f7ddb14SDimitry Andric 
1550*5f7ddb14SDimitry Andric   // Returns the content of the choice as a string.
GetChoiceContent()1551*5f7ddb14SDimitry Andric   std::string GetChoiceContent() { return m_choices[m_choice]; }
1552*5f7ddb14SDimitry Andric 
1553*5f7ddb14SDimitry Andric   // Returns the index of the choice.
GetChoice()1554*5f7ddb14SDimitry Andric   int GetChoice() { return m_choice; }
1555*5f7ddb14SDimitry Andric 
SetChoice(const std::string & choice)1556*5f7ddb14SDimitry Andric   void SetChoice(const std::string &choice) {
1557*5f7ddb14SDimitry Andric     for (int i = 0; i < GetNumberOfChoices(); i++) {
1558*5f7ddb14SDimitry Andric       if (choice == m_choices[i]) {
1559*5f7ddb14SDimitry Andric         m_choice = i;
1560*5f7ddb14SDimitry Andric         return;
1561*5f7ddb14SDimitry Andric       }
1562*5f7ddb14SDimitry Andric     }
1563*5f7ddb14SDimitry Andric   }
1564*5f7ddb14SDimitry Andric 
1565*5f7ddb14SDimitry Andric protected:
1566*5f7ddb14SDimitry Andric   std::string m_label;
1567*5f7ddb14SDimitry Andric   int m_number_of_visible_choices;
1568*5f7ddb14SDimitry Andric   std::vector<std::string> m_choices;
1569*5f7ddb14SDimitry Andric   // The index of the selected choice.
1570*5f7ddb14SDimitry Andric   int m_choice;
1571*5f7ddb14SDimitry Andric   // The index of the first visible choice in the field.
1572*5f7ddb14SDimitry Andric   int m_first_visibile_choice;
1573*5f7ddb14SDimitry Andric };
1574*5f7ddb14SDimitry Andric 
1575*5f7ddb14SDimitry Andric class PlatformPluginFieldDelegate : public ChoicesFieldDelegate {
1576*5f7ddb14SDimitry Andric public:
PlatformPluginFieldDelegate(Debugger & debugger)1577*5f7ddb14SDimitry Andric   PlatformPluginFieldDelegate(Debugger &debugger)
1578*5f7ddb14SDimitry Andric       : ChoicesFieldDelegate("Platform Plugin", 3, GetPossiblePluginNames()) {
1579*5f7ddb14SDimitry Andric     PlatformSP platform_sp = debugger.GetPlatformList().GetSelectedPlatform();
1580*5f7ddb14SDimitry Andric     if (platform_sp)
1581*5f7ddb14SDimitry Andric       SetChoice(platform_sp->GetName().AsCString());
1582*5f7ddb14SDimitry Andric   }
1583*5f7ddb14SDimitry Andric 
GetPossiblePluginNames()1584*5f7ddb14SDimitry Andric   std::vector<std::string> GetPossiblePluginNames() {
1585*5f7ddb14SDimitry Andric     std::vector<std::string> names;
1586*5f7ddb14SDimitry Andric     size_t i = 0;
1587*5f7ddb14SDimitry Andric     while (auto name = PluginManager::GetPlatformPluginNameAtIndex(i++))
1588*5f7ddb14SDimitry Andric       names.push_back(name);
1589*5f7ddb14SDimitry Andric     return names;
1590*5f7ddb14SDimitry Andric   }
1591*5f7ddb14SDimitry Andric 
GetPluginName()1592*5f7ddb14SDimitry Andric   std::string GetPluginName() {
1593*5f7ddb14SDimitry Andric     std::string plugin_name = GetChoiceContent();
1594*5f7ddb14SDimitry Andric     return plugin_name;
1595*5f7ddb14SDimitry Andric   }
1596*5f7ddb14SDimitry Andric };
1597*5f7ddb14SDimitry Andric 
1598*5f7ddb14SDimitry Andric class ProcessPluginFieldDelegate : public ChoicesFieldDelegate {
1599*5f7ddb14SDimitry Andric public:
ProcessPluginFieldDelegate()1600*5f7ddb14SDimitry Andric   ProcessPluginFieldDelegate()
1601*5f7ddb14SDimitry Andric       : ChoicesFieldDelegate("Process Plugin", 3, GetPossiblePluginNames()) {}
1602*5f7ddb14SDimitry Andric 
GetPossiblePluginNames()1603*5f7ddb14SDimitry Andric   std::vector<std::string> GetPossiblePluginNames() {
1604*5f7ddb14SDimitry Andric     std::vector<std::string> names;
1605*5f7ddb14SDimitry Andric     names.push_back("<default>");
1606*5f7ddb14SDimitry Andric 
1607*5f7ddb14SDimitry Andric     size_t i = 0;
1608*5f7ddb14SDimitry Andric     while (auto name = PluginManager::GetProcessPluginNameAtIndex(i++))
1609*5f7ddb14SDimitry Andric       names.push_back(name);
1610*5f7ddb14SDimitry Andric     return names;
1611*5f7ddb14SDimitry Andric   }
1612*5f7ddb14SDimitry Andric 
GetPluginName()1613*5f7ddb14SDimitry Andric   std::string GetPluginName() {
1614*5f7ddb14SDimitry Andric     std::string plugin_name = GetChoiceContent();
1615*5f7ddb14SDimitry Andric     if (plugin_name == "<default>")
1616*5f7ddb14SDimitry Andric       return "";
1617*5f7ddb14SDimitry Andric     return plugin_name;
1618*5f7ddb14SDimitry Andric   }
1619*5f7ddb14SDimitry Andric };
1620*5f7ddb14SDimitry Andric 
1621*5f7ddb14SDimitry Andric template <class T> class ListFieldDelegate : public FieldDelegate {
1622*5f7ddb14SDimitry Andric public:
ListFieldDelegate(const char * label,T default_field)1623*5f7ddb14SDimitry Andric   ListFieldDelegate(const char *label, T default_field)
1624*5f7ddb14SDimitry Andric       : m_label(label), m_default_field(default_field), m_selection_index(0),
1625*5f7ddb14SDimitry Andric         m_selection_type(SelectionType::NewButton) {}
1626*5f7ddb14SDimitry Andric 
1627*5f7ddb14SDimitry Andric   // Signify which element is selected. If a field or a remove button is
1628*5f7ddb14SDimitry Andric   // selected, then m_selection_index signifies the particular field that
1629*5f7ddb14SDimitry Andric   // is selected or the field that the remove button belongs to.
1630*5f7ddb14SDimitry Andric   enum class SelectionType { Field, RemoveButton, NewButton };
1631*5f7ddb14SDimitry Andric 
1632*5f7ddb14SDimitry Andric   // A List field is drawn as a titled box of a number of other fields of the
1633*5f7ddb14SDimitry Andric   // same type. Each field has a Remove button next to it that removes the
1634*5f7ddb14SDimitry Andric   // corresponding field. Finally, the last line contains a New button to add a
1635*5f7ddb14SDimitry Andric   // new field.
1636*5f7ddb14SDimitry Andric   //
1637*5f7ddb14SDimitry Andric   // __[Label]___________
1638*5f7ddb14SDimitry Andric   // | Field 0 [Remove] |
1639*5f7ddb14SDimitry Andric   // | Field 1 [Remove] |
1640*5f7ddb14SDimitry Andric   // | Field 2 [Remove] |
1641*5f7ddb14SDimitry Andric   // |      [New]       |
1642*5f7ddb14SDimitry Andric   // |__________________|
1643*5f7ddb14SDimitry Andric 
1644*5f7ddb14SDimitry Andric   // List fields have two lines for border characters, 1 line for the New
1645*5f7ddb14SDimitry Andric   // button, and the total height of the available fields.
FieldDelegateGetHeight()1646*5f7ddb14SDimitry Andric   int FieldDelegateGetHeight() override {
1647*5f7ddb14SDimitry Andric     // 2 border characters.
1648*5f7ddb14SDimitry Andric     int height = 2;
1649*5f7ddb14SDimitry Andric     // Total height of the fields.
1650*5f7ddb14SDimitry Andric     for (int i = 0; i < GetNumberOfFields(); i++) {
1651*5f7ddb14SDimitry Andric       height += m_fields[i].FieldDelegateGetHeight();
1652*5f7ddb14SDimitry Andric     }
1653*5f7ddb14SDimitry Andric     // A line for the New button.
1654*5f7ddb14SDimitry Andric     height++;
1655*5f7ddb14SDimitry Andric     return height;
1656*5f7ddb14SDimitry Andric   }
1657*5f7ddb14SDimitry Andric 
FieldDelegateGetScrollContext()1658*5f7ddb14SDimitry Andric   ScrollContext FieldDelegateGetScrollContext() override {
1659*5f7ddb14SDimitry Andric     int height = FieldDelegateGetHeight();
1660*5f7ddb14SDimitry Andric     if (m_selection_type == SelectionType::NewButton)
1661*5f7ddb14SDimitry Andric       return ScrollContext(height - 2, height - 1);
1662*5f7ddb14SDimitry Andric 
1663*5f7ddb14SDimitry Andric     FieldDelegate &field = m_fields[m_selection_index];
1664*5f7ddb14SDimitry Andric     ScrollContext context = field.FieldDelegateGetScrollContext();
1665*5f7ddb14SDimitry Andric 
1666*5f7ddb14SDimitry Andric     // Start at 1 because of the top border.
1667*5f7ddb14SDimitry Andric     int offset = 1;
1668*5f7ddb14SDimitry Andric     for (int i = 0; i < m_selection_index; i++) {
1669*5f7ddb14SDimitry Andric       offset += m_fields[i].FieldDelegateGetHeight();
1670*5f7ddb14SDimitry Andric     }
1671*5f7ddb14SDimitry Andric     context.Offset(offset);
1672*5f7ddb14SDimitry Andric 
1673*5f7ddb14SDimitry Andric     // If the scroll context is touching the top border, include it in the
1674*5f7ddb14SDimitry Andric     // context to show the label.
1675*5f7ddb14SDimitry Andric     if (context.start == 1)
1676*5f7ddb14SDimitry Andric       context.start--;
1677*5f7ddb14SDimitry Andric 
1678*5f7ddb14SDimitry Andric     // If the scroll context is touching the new button, include it as well as
1679*5f7ddb14SDimitry Andric     // the bottom border in the context.
1680*5f7ddb14SDimitry Andric     if (context.end == height - 3)
1681*5f7ddb14SDimitry Andric       context.end += 2;
1682*5f7ddb14SDimitry Andric 
1683*5f7ddb14SDimitry Andric     return context;
1684*5f7ddb14SDimitry Andric   }
1685*5f7ddb14SDimitry Andric 
DrawRemoveButton(SubPad & surface,int highlight)1686*5f7ddb14SDimitry Andric   void DrawRemoveButton(SubPad &surface, int highlight) {
1687*5f7ddb14SDimitry Andric     surface.MoveCursor(1, surface.GetHeight() / 2);
1688*5f7ddb14SDimitry Andric     if (highlight)
1689*5f7ddb14SDimitry Andric       surface.AttributeOn(A_REVERSE);
1690*5f7ddb14SDimitry Andric     surface.PutCString("[Remove]");
1691*5f7ddb14SDimitry Andric     if (highlight)
1692*5f7ddb14SDimitry Andric       surface.AttributeOff(A_REVERSE);
1693*5f7ddb14SDimitry Andric   }
1694*5f7ddb14SDimitry Andric 
DrawFields(SubPad & surface,bool is_selected)1695*5f7ddb14SDimitry Andric   void DrawFields(SubPad &surface, bool is_selected) {
1696*5f7ddb14SDimitry Andric     int line = 0;
1697*5f7ddb14SDimitry Andric     int width = surface.GetWidth();
1698*5f7ddb14SDimitry Andric     for (int i = 0; i < GetNumberOfFields(); i++) {
1699*5f7ddb14SDimitry Andric       int height = m_fields[i].FieldDelegateGetHeight();
1700*5f7ddb14SDimitry Andric       Rect bounds = Rect(Point(0, line), Size(width, height));
1701*5f7ddb14SDimitry Andric       Rect field_bounds, remove_button_bounds;
1702*5f7ddb14SDimitry Andric       bounds.VerticalSplit(bounds.size.width - sizeof(" [Remove]"),
1703*5f7ddb14SDimitry Andric                            field_bounds, remove_button_bounds);
1704*5f7ddb14SDimitry Andric       SubPad field_surface = SubPad(surface, field_bounds);
1705*5f7ddb14SDimitry Andric       SubPad remove_button_surface = SubPad(surface, remove_button_bounds);
1706*5f7ddb14SDimitry Andric 
1707*5f7ddb14SDimitry Andric       bool is_element_selected = m_selection_index == i && is_selected;
1708*5f7ddb14SDimitry Andric       bool is_field_selected =
1709*5f7ddb14SDimitry Andric           is_element_selected && m_selection_type == SelectionType::Field;
1710*5f7ddb14SDimitry Andric       bool is_remove_button_selected =
1711*5f7ddb14SDimitry Andric           is_element_selected &&
1712*5f7ddb14SDimitry Andric           m_selection_type == SelectionType::RemoveButton;
1713*5f7ddb14SDimitry Andric       m_fields[i].FieldDelegateDraw(field_surface, is_field_selected);
1714*5f7ddb14SDimitry Andric       DrawRemoveButton(remove_button_surface, is_remove_button_selected);
1715*5f7ddb14SDimitry Andric 
1716*5f7ddb14SDimitry Andric       line += height;
1717*5f7ddb14SDimitry Andric     }
1718*5f7ddb14SDimitry Andric   }
1719*5f7ddb14SDimitry Andric 
DrawNewButton(SubPad & surface,bool is_selected)1720*5f7ddb14SDimitry Andric   void DrawNewButton(SubPad &surface, bool is_selected) {
1721*5f7ddb14SDimitry Andric     const char *button_text = "[New]";
1722*5f7ddb14SDimitry Andric     int x = (surface.GetWidth() - sizeof(button_text) - 1) / 2;
1723*5f7ddb14SDimitry Andric     surface.MoveCursor(x, 0);
1724*5f7ddb14SDimitry Andric     bool highlight =
1725*5f7ddb14SDimitry Andric         is_selected && m_selection_type == SelectionType::NewButton;
1726*5f7ddb14SDimitry Andric     if (highlight)
1727*5f7ddb14SDimitry Andric       surface.AttributeOn(A_REVERSE);
1728*5f7ddb14SDimitry Andric     surface.PutCString(button_text);
1729*5f7ddb14SDimitry Andric     if (highlight)
1730*5f7ddb14SDimitry Andric       surface.AttributeOff(A_REVERSE);
1731*5f7ddb14SDimitry Andric   }
1732*5f7ddb14SDimitry Andric 
FieldDelegateDraw(SubPad & surface,bool is_selected)1733*5f7ddb14SDimitry Andric   void FieldDelegateDraw(SubPad &surface, bool is_selected) override {
1734*5f7ddb14SDimitry Andric     surface.TitledBox(m_label.c_str());
1735*5f7ddb14SDimitry Andric 
1736*5f7ddb14SDimitry Andric     Rect content_bounds = surface.GetFrame();
1737*5f7ddb14SDimitry Andric     content_bounds.Inset(1, 1);
1738*5f7ddb14SDimitry Andric     Rect fields_bounds, new_button_bounds;
1739*5f7ddb14SDimitry Andric     content_bounds.HorizontalSplit(content_bounds.size.height - 1,
1740*5f7ddb14SDimitry Andric                                    fields_bounds, new_button_bounds);
1741*5f7ddb14SDimitry Andric     SubPad fields_surface = SubPad(surface, fields_bounds);
1742*5f7ddb14SDimitry Andric     SubPad new_button_surface = SubPad(surface, new_button_bounds);
1743*5f7ddb14SDimitry Andric 
1744*5f7ddb14SDimitry Andric     DrawFields(fields_surface, is_selected);
1745*5f7ddb14SDimitry Andric     DrawNewButton(new_button_surface, is_selected);
1746*5f7ddb14SDimitry Andric   }
1747*5f7ddb14SDimitry Andric 
AddNewField()1748*5f7ddb14SDimitry Andric   void AddNewField() {
1749*5f7ddb14SDimitry Andric     m_fields.push_back(m_default_field);
1750*5f7ddb14SDimitry Andric     m_selection_index = GetNumberOfFields() - 1;
1751*5f7ddb14SDimitry Andric     m_selection_type = SelectionType::Field;
1752*5f7ddb14SDimitry Andric     FieldDelegate &field = m_fields[m_selection_index];
1753*5f7ddb14SDimitry Andric     field.FieldDelegateSelectFirstElement();
1754*5f7ddb14SDimitry Andric   }
1755*5f7ddb14SDimitry Andric 
RemoveField()1756*5f7ddb14SDimitry Andric   void RemoveField() {
1757*5f7ddb14SDimitry Andric     m_fields.erase(m_fields.begin() + m_selection_index);
1758*5f7ddb14SDimitry Andric     if (m_selection_index != 0)
1759*5f7ddb14SDimitry Andric       m_selection_index--;
1760*5f7ddb14SDimitry Andric 
1761*5f7ddb14SDimitry Andric     if (GetNumberOfFields() > 0) {
1762*5f7ddb14SDimitry Andric       m_selection_type = SelectionType::Field;
1763*5f7ddb14SDimitry Andric       FieldDelegate &field = m_fields[m_selection_index];
1764*5f7ddb14SDimitry Andric       field.FieldDelegateSelectFirstElement();
1765*5f7ddb14SDimitry Andric     } else
1766*5f7ddb14SDimitry Andric       m_selection_type = SelectionType::NewButton;
1767*5f7ddb14SDimitry Andric   }
1768*5f7ddb14SDimitry Andric 
SelectNext(int key)1769*5f7ddb14SDimitry Andric   HandleCharResult SelectNext(int key) {
1770*5f7ddb14SDimitry Andric     if (m_selection_type == SelectionType::NewButton)
1771*5f7ddb14SDimitry Andric       return eKeyNotHandled;
1772*5f7ddb14SDimitry Andric 
1773*5f7ddb14SDimitry Andric     if (m_selection_type == SelectionType::RemoveButton) {
1774*5f7ddb14SDimitry Andric       if (m_selection_index == GetNumberOfFields() - 1) {
1775*5f7ddb14SDimitry Andric         m_selection_type = SelectionType::NewButton;
1776*5f7ddb14SDimitry Andric         return eKeyHandled;
1777*5f7ddb14SDimitry Andric       }
1778*5f7ddb14SDimitry Andric       m_selection_index++;
1779*5f7ddb14SDimitry Andric       m_selection_type = SelectionType::Field;
1780*5f7ddb14SDimitry Andric       FieldDelegate &next_field = m_fields[m_selection_index];
1781*5f7ddb14SDimitry Andric       next_field.FieldDelegateSelectFirstElement();
1782*5f7ddb14SDimitry Andric       return eKeyHandled;
1783*5f7ddb14SDimitry Andric     }
1784*5f7ddb14SDimitry Andric 
1785*5f7ddb14SDimitry Andric     FieldDelegate &field = m_fields[m_selection_index];
1786*5f7ddb14SDimitry Andric     if (!field.FieldDelegateOnLastOrOnlyElement()) {
1787*5f7ddb14SDimitry Andric       return field.FieldDelegateHandleChar(key);
1788*5f7ddb14SDimitry Andric     }
1789*5f7ddb14SDimitry Andric 
1790*5f7ddb14SDimitry Andric     field.FieldDelegateExitCallback();
1791*5f7ddb14SDimitry Andric 
1792*5f7ddb14SDimitry Andric     m_selection_type = SelectionType::RemoveButton;
1793*5f7ddb14SDimitry Andric     return eKeyHandled;
1794*5f7ddb14SDimitry Andric   }
1795*5f7ddb14SDimitry Andric 
SelectPrevious(int key)1796*5f7ddb14SDimitry Andric   HandleCharResult SelectPrevious(int key) {
1797*5f7ddb14SDimitry Andric     if (FieldDelegateOnFirstOrOnlyElement())
1798*5f7ddb14SDimitry Andric       return eKeyNotHandled;
1799*5f7ddb14SDimitry Andric 
1800*5f7ddb14SDimitry Andric     if (m_selection_type == SelectionType::RemoveButton) {
1801*5f7ddb14SDimitry Andric       m_selection_type = SelectionType::Field;
1802*5f7ddb14SDimitry Andric       FieldDelegate &field = m_fields[m_selection_index];
1803*5f7ddb14SDimitry Andric       field.FieldDelegateSelectLastElement();
1804*5f7ddb14SDimitry Andric       return eKeyHandled;
1805*5f7ddb14SDimitry Andric     }
1806*5f7ddb14SDimitry Andric 
1807*5f7ddb14SDimitry Andric     if (m_selection_type == SelectionType::NewButton) {
1808*5f7ddb14SDimitry Andric       m_selection_type = SelectionType::RemoveButton;
1809*5f7ddb14SDimitry Andric       m_selection_index = GetNumberOfFields() - 1;
1810*5f7ddb14SDimitry Andric       return eKeyHandled;
1811*5f7ddb14SDimitry Andric     }
1812*5f7ddb14SDimitry Andric 
1813*5f7ddb14SDimitry Andric     FieldDelegate &field = m_fields[m_selection_index];
1814*5f7ddb14SDimitry Andric     if (!field.FieldDelegateOnFirstOrOnlyElement()) {
1815*5f7ddb14SDimitry Andric       return field.FieldDelegateHandleChar(key);
1816*5f7ddb14SDimitry Andric     }
1817*5f7ddb14SDimitry Andric 
1818*5f7ddb14SDimitry Andric     field.FieldDelegateExitCallback();
1819*5f7ddb14SDimitry Andric 
1820*5f7ddb14SDimitry Andric     m_selection_type = SelectionType::RemoveButton;
1821*5f7ddb14SDimitry Andric     m_selection_index--;
1822*5f7ddb14SDimitry Andric     return eKeyHandled;
1823*5f7ddb14SDimitry Andric   }
1824*5f7ddb14SDimitry Andric 
FieldDelegateHandleChar(int key)1825*5f7ddb14SDimitry Andric   HandleCharResult FieldDelegateHandleChar(int key) override {
1826*5f7ddb14SDimitry Andric     switch (key) {
1827*5f7ddb14SDimitry Andric     case '\r':
1828*5f7ddb14SDimitry Andric     case '\n':
1829*5f7ddb14SDimitry Andric     case KEY_ENTER:
1830*5f7ddb14SDimitry Andric       switch (m_selection_type) {
1831*5f7ddb14SDimitry Andric       case SelectionType::NewButton:
1832*5f7ddb14SDimitry Andric         AddNewField();
1833*5f7ddb14SDimitry Andric         return eKeyHandled;
1834*5f7ddb14SDimitry Andric       case SelectionType::RemoveButton:
1835*5f7ddb14SDimitry Andric         RemoveField();
1836*5f7ddb14SDimitry Andric         return eKeyHandled;
1837*5f7ddb14SDimitry Andric       default:
1838*5f7ddb14SDimitry Andric         break;
1839*5f7ddb14SDimitry Andric       }
1840*5f7ddb14SDimitry Andric       break;
1841*5f7ddb14SDimitry Andric     case '\t':
1842*5f7ddb14SDimitry Andric       SelectNext(key);
1843*5f7ddb14SDimitry Andric       return eKeyHandled;
1844*5f7ddb14SDimitry Andric     case KEY_SHIFT_TAB:
1845*5f7ddb14SDimitry Andric       SelectPrevious(key);
1846*5f7ddb14SDimitry Andric       return eKeyHandled;
1847*5f7ddb14SDimitry Andric     default:
1848*5f7ddb14SDimitry Andric       break;
1849*5f7ddb14SDimitry Andric     }
1850*5f7ddb14SDimitry Andric 
1851*5f7ddb14SDimitry Andric     // If the key wasn't handled and one of the fields is selected, pass the key
1852*5f7ddb14SDimitry Andric     // to that field.
1853*5f7ddb14SDimitry Andric     if (m_selection_type == SelectionType::Field) {
1854*5f7ddb14SDimitry Andric       return m_fields[m_selection_index].FieldDelegateHandleChar(key);
1855*5f7ddb14SDimitry Andric     }
1856*5f7ddb14SDimitry Andric 
1857*5f7ddb14SDimitry Andric     return eKeyNotHandled;
1858*5f7ddb14SDimitry Andric   }
1859*5f7ddb14SDimitry Andric 
FieldDelegateOnLastOrOnlyElement()1860*5f7ddb14SDimitry Andric   bool FieldDelegateOnLastOrOnlyElement() override {
1861*5f7ddb14SDimitry Andric     if (m_selection_type == SelectionType::NewButton) {
1862*5f7ddb14SDimitry Andric       return true;
1863*5f7ddb14SDimitry Andric     }
1864*5f7ddb14SDimitry Andric     return false;
1865*5f7ddb14SDimitry Andric   }
1866*5f7ddb14SDimitry Andric 
FieldDelegateOnFirstOrOnlyElement()1867*5f7ddb14SDimitry Andric   bool FieldDelegateOnFirstOrOnlyElement() override {
1868*5f7ddb14SDimitry Andric     if (m_selection_type == SelectionType::NewButton &&
1869*5f7ddb14SDimitry Andric         GetNumberOfFields() == 0)
1870*5f7ddb14SDimitry Andric       return true;
1871*5f7ddb14SDimitry Andric 
1872*5f7ddb14SDimitry Andric     if (m_selection_type == SelectionType::Field && m_selection_index == 0) {
1873*5f7ddb14SDimitry Andric       FieldDelegate &field = m_fields[m_selection_index];
1874*5f7ddb14SDimitry Andric       return field.FieldDelegateOnFirstOrOnlyElement();
1875*5f7ddb14SDimitry Andric     }
1876*5f7ddb14SDimitry Andric 
1877*5f7ddb14SDimitry Andric     return false;
1878*5f7ddb14SDimitry Andric   }
1879*5f7ddb14SDimitry Andric 
FieldDelegateSelectFirstElement()1880*5f7ddb14SDimitry Andric   void FieldDelegateSelectFirstElement() override {
1881*5f7ddb14SDimitry Andric     if (GetNumberOfFields() == 0) {
1882*5f7ddb14SDimitry Andric       m_selection_type = SelectionType::NewButton;
1883*5f7ddb14SDimitry Andric       return;
1884*5f7ddb14SDimitry Andric     }
1885*5f7ddb14SDimitry Andric 
1886*5f7ddb14SDimitry Andric     m_selection_type = SelectionType::Field;
1887*5f7ddb14SDimitry Andric     m_selection_index = 0;
1888*5f7ddb14SDimitry Andric   }
1889*5f7ddb14SDimitry Andric 
FieldDelegateSelectLastElement()1890*5f7ddb14SDimitry Andric   void FieldDelegateSelectLastElement() override {
1891*5f7ddb14SDimitry Andric     m_selection_type = SelectionType::NewButton;
1892*5f7ddb14SDimitry Andric     return;
1893*5f7ddb14SDimitry Andric   }
1894*5f7ddb14SDimitry Andric 
GetNumberOfFields()1895*5f7ddb14SDimitry Andric   int GetNumberOfFields() { return m_fields.size(); }
1896*5f7ddb14SDimitry Andric 
1897*5f7ddb14SDimitry Andric   // Returns the form delegate at the current index.
GetField(int index)1898*5f7ddb14SDimitry Andric   T &GetField(int index) { return m_fields[index]; }
1899*5f7ddb14SDimitry Andric 
1900*5f7ddb14SDimitry Andric protected:
1901*5f7ddb14SDimitry Andric   std::string m_label;
1902*5f7ddb14SDimitry Andric   // The default field delegate instance from which new field delegates will be
1903*5f7ddb14SDimitry Andric   // created though a copy.
1904*5f7ddb14SDimitry Andric   T m_default_field;
1905*5f7ddb14SDimitry Andric   std::vector<T> m_fields;
1906*5f7ddb14SDimitry Andric   int m_selection_index;
1907*5f7ddb14SDimitry Andric   // See SelectionType class enum.
1908*5f7ddb14SDimitry Andric   SelectionType m_selection_type;
1909*5f7ddb14SDimitry Andric };
1910*5f7ddb14SDimitry Andric 
1911*5f7ddb14SDimitry Andric class FormAction {
1912*5f7ddb14SDimitry Andric public:
FormAction(const char * label,std::function<void (Window &)> action)1913*5f7ddb14SDimitry Andric   FormAction(const char *label, std::function<void(Window &)> action)
1914*5f7ddb14SDimitry Andric       : m_action(action) {
1915*5f7ddb14SDimitry Andric     if (label)
1916*5f7ddb14SDimitry Andric       m_label = label;
1917*5f7ddb14SDimitry Andric   }
1918*5f7ddb14SDimitry Andric 
1919*5f7ddb14SDimitry Andric   // Draw a centered [Label].
Draw(SubPad & surface,bool is_selected)1920*5f7ddb14SDimitry Andric   void Draw(SubPad &surface, bool is_selected) {
1921*5f7ddb14SDimitry Andric     int x = (surface.GetWidth() - m_label.length()) / 2;
1922*5f7ddb14SDimitry Andric     surface.MoveCursor(x, 0);
1923*5f7ddb14SDimitry Andric     if (is_selected)
1924*5f7ddb14SDimitry Andric       surface.AttributeOn(A_REVERSE);
1925*5f7ddb14SDimitry Andric     surface.PutChar('[');
1926*5f7ddb14SDimitry Andric     surface.PutCString(m_label.c_str());
1927*5f7ddb14SDimitry Andric     surface.PutChar(']');
1928*5f7ddb14SDimitry Andric     if (is_selected)
1929*5f7ddb14SDimitry Andric       surface.AttributeOff(A_REVERSE);
1930*5f7ddb14SDimitry Andric   }
1931*5f7ddb14SDimitry Andric 
Execute(Window & window)1932*5f7ddb14SDimitry Andric   void Execute(Window &window) { m_action(window); }
1933*5f7ddb14SDimitry Andric 
GetLabel()1934*5f7ddb14SDimitry Andric   const std::string &GetLabel() { return m_label; }
1935*5f7ddb14SDimitry Andric 
1936*5f7ddb14SDimitry Andric protected:
1937*5f7ddb14SDimitry Andric   std::string m_label;
1938*5f7ddb14SDimitry Andric   std::function<void(Window &)> m_action;
1939*5f7ddb14SDimitry Andric };
1940*5f7ddb14SDimitry Andric 
1941*5f7ddb14SDimitry Andric class FormDelegate {
1942*5f7ddb14SDimitry Andric public:
FormDelegate()1943*5f7ddb14SDimitry Andric   FormDelegate() {}
1944*5f7ddb14SDimitry Andric 
1945*5f7ddb14SDimitry Andric   virtual ~FormDelegate() = default;
1946*5f7ddb14SDimitry Andric 
1947*5f7ddb14SDimitry Andric   virtual std::string GetName() = 0;
1948*5f7ddb14SDimitry Andric 
UpdateFieldsVisibility()1949*5f7ddb14SDimitry Andric   virtual void UpdateFieldsVisibility() { return; }
1950*5f7ddb14SDimitry Andric 
GetField(uint32_t field_index)1951*5f7ddb14SDimitry Andric   FieldDelegate *GetField(uint32_t field_index) {
1952*5f7ddb14SDimitry Andric     if (field_index < m_fields.size())
1953*5f7ddb14SDimitry Andric       return m_fields[field_index].get();
1954*5f7ddb14SDimitry Andric     return nullptr;
1955*5f7ddb14SDimitry Andric   }
1956*5f7ddb14SDimitry Andric 
GetAction(int action_index)1957*5f7ddb14SDimitry Andric   FormAction &GetAction(int action_index) { return m_actions[action_index]; }
1958*5f7ddb14SDimitry Andric 
GetNumberOfFields()1959*5f7ddb14SDimitry Andric   int GetNumberOfFields() { return m_fields.size(); }
1960*5f7ddb14SDimitry Andric 
GetNumberOfActions()1961*5f7ddb14SDimitry Andric   int GetNumberOfActions() { return m_actions.size(); }
1962*5f7ddb14SDimitry Andric 
HasError()1963*5f7ddb14SDimitry Andric   bool HasError() { return !m_error.empty(); }
1964*5f7ddb14SDimitry Andric 
ClearError()1965*5f7ddb14SDimitry Andric   void ClearError() { m_error.clear(); }
1966*5f7ddb14SDimitry Andric 
GetError()1967*5f7ddb14SDimitry Andric   const std::string &GetError() { return m_error; }
1968*5f7ddb14SDimitry Andric 
SetError(const char * error)1969*5f7ddb14SDimitry Andric   void SetError(const char *error) { m_error = error; }
1970*5f7ddb14SDimitry Andric 
1971*5f7ddb14SDimitry Andric   // If all fields are valid, true is returned. Otherwise, an error message is
1972*5f7ddb14SDimitry Andric   // set and false is returned. This method is usually called at the start of an
1973*5f7ddb14SDimitry Andric   // action that requires valid fields.
CheckFieldsValidity()1974*5f7ddb14SDimitry Andric   bool CheckFieldsValidity() {
1975*5f7ddb14SDimitry Andric     for (int i = 0; i < GetNumberOfFields(); i++) {
1976*5f7ddb14SDimitry Andric       if (GetField(i)->FieldDelegateHasError()) {
1977*5f7ddb14SDimitry Andric         SetError("Some fields are invalid!");
1978*5f7ddb14SDimitry Andric         return false;
1979*5f7ddb14SDimitry Andric       }
1980*5f7ddb14SDimitry Andric     }
1981*5f7ddb14SDimitry Andric     return true;
1982*5f7ddb14SDimitry Andric   }
1983*5f7ddb14SDimitry Andric 
1984*5f7ddb14SDimitry Andric   // Factory methods to create and add fields of specific types.
1985*5f7ddb14SDimitry Andric 
AddTextField(const char * label,const char * content,bool required)1986*5f7ddb14SDimitry Andric   TextFieldDelegate *AddTextField(const char *label, const char *content,
1987*5f7ddb14SDimitry Andric                                   bool required) {
1988*5f7ddb14SDimitry Andric     TextFieldDelegate *delegate =
1989*5f7ddb14SDimitry Andric         new TextFieldDelegate(label, content, required);
1990*5f7ddb14SDimitry Andric     m_fields.push_back(FieldDelegateUP(delegate));
1991*5f7ddb14SDimitry Andric     return delegate;
1992*5f7ddb14SDimitry Andric   }
1993*5f7ddb14SDimitry Andric 
AddFileField(const char * label,const char * content,bool need_to_exist,bool required)1994*5f7ddb14SDimitry Andric   FileFieldDelegate *AddFileField(const char *label, const char *content,
1995*5f7ddb14SDimitry Andric                                   bool need_to_exist, bool required) {
1996*5f7ddb14SDimitry Andric     FileFieldDelegate *delegate =
1997*5f7ddb14SDimitry Andric         new FileFieldDelegate(label, content, need_to_exist, required);
1998*5f7ddb14SDimitry Andric     m_fields.push_back(FieldDelegateUP(delegate));
1999*5f7ddb14SDimitry Andric     return delegate;
2000*5f7ddb14SDimitry Andric   }
2001*5f7ddb14SDimitry Andric 
AddDirectoryField(const char * label,const char * content,bool need_to_exist,bool required)2002*5f7ddb14SDimitry Andric   DirectoryFieldDelegate *AddDirectoryField(const char *label,
2003*5f7ddb14SDimitry Andric                                             const char *content,
2004*5f7ddb14SDimitry Andric                                             bool need_to_exist, bool required) {
2005*5f7ddb14SDimitry Andric     DirectoryFieldDelegate *delegate =
2006*5f7ddb14SDimitry Andric         new DirectoryFieldDelegate(label, content, need_to_exist, required);
2007*5f7ddb14SDimitry Andric     m_fields.push_back(FieldDelegateUP(delegate));
2008*5f7ddb14SDimitry Andric     return delegate;
2009*5f7ddb14SDimitry Andric   }
2010*5f7ddb14SDimitry Andric 
AddArchField(const char * label,const char * content,bool required)2011*5f7ddb14SDimitry Andric   ArchFieldDelegate *AddArchField(const char *label, const char *content,
2012*5f7ddb14SDimitry Andric                                   bool required) {
2013*5f7ddb14SDimitry Andric     ArchFieldDelegate *delegate =
2014*5f7ddb14SDimitry Andric         new ArchFieldDelegate(label, content, required);
2015*5f7ddb14SDimitry Andric     m_fields.push_back(FieldDelegateUP(delegate));
2016*5f7ddb14SDimitry Andric     return delegate;
2017*5f7ddb14SDimitry Andric   }
2018*5f7ddb14SDimitry Andric 
AddIntegerField(const char * label,int content,bool required)2019*5f7ddb14SDimitry Andric   IntegerFieldDelegate *AddIntegerField(const char *label, int content,
2020*5f7ddb14SDimitry Andric                                         bool required) {
2021*5f7ddb14SDimitry Andric     IntegerFieldDelegate *delegate =
2022*5f7ddb14SDimitry Andric         new IntegerFieldDelegate(label, content, required);
2023*5f7ddb14SDimitry Andric     m_fields.push_back(FieldDelegateUP(delegate));
2024*5f7ddb14SDimitry Andric     return delegate;
2025*5f7ddb14SDimitry Andric   }
2026*5f7ddb14SDimitry Andric 
AddBooleanField(const char * label,bool content)2027*5f7ddb14SDimitry Andric   BooleanFieldDelegate *AddBooleanField(const char *label, bool content) {
2028*5f7ddb14SDimitry Andric     BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content);
2029*5f7ddb14SDimitry Andric     m_fields.push_back(FieldDelegateUP(delegate));
2030*5f7ddb14SDimitry Andric     return delegate;
2031*5f7ddb14SDimitry Andric   }
2032*5f7ddb14SDimitry Andric 
AddChoicesField(const char * label,int height,std::vector<std::string> choices)2033*5f7ddb14SDimitry Andric   ChoicesFieldDelegate *AddChoicesField(const char *label, int height,
2034*5f7ddb14SDimitry Andric                                         std::vector<std::string> choices) {
2035*5f7ddb14SDimitry Andric     ChoicesFieldDelegate *delegate =
2036*5f7ddb14SDimitry Andric         new ChoicesFieldDelegate(label, height, choices);
2037*5f7ddb14SDimitry Andric     m_fields.push_back(FieldDelegateUP(delegate));
2038*5f7ddb14SDimitry Andric     return delegate;
2039*5f7ddb14SDimitry Andric   }
2040*5f7ddb14SDimitry Andric 
AddPlatformPluginField(Debugger & debugger)2041*5f7ddb14SDimitry Andric   PlatformPluginFieldDelegate *AddPlatformPluginField(Debugger &debugger) {
2042*5f7ddb14SDimitry Andric     PlatformPluginFieldDelegate *delegate =
2043*5f7ddb14SDimitry Andric         new PlatformPluginFieldDelegate(debugger);
2044*5f7ddb14SDimitry Andric     m_fields.push_back(FieldDelegateUP(delegate));
2045*5f7ddb14SDimitry Andric     return delegate;
2046*5f7ddb14SDimitry Andric   }
2047*5f7ddb14SDimitry Andric 
AddProcessPluginField()2048*5f7ddb14SDimitry Andric   ProcessPluginFieldDelegate *AddProcessPluginField() {
2049*5f7ddb14SDimitry Andric     ProcessPluginFieldDelegate *delegate = new ProcessPluginFieldDelegate();
2050*5f7ddb14SDimitry Andric     m_fields.push_back(FieldDelegateUP(delegate));
2051*5f7ddb14SDimitry Andric     return delegate;
2052*5f7ddb14SDimitry Andric   }
2053*5f7ddb14SDimitry Andric 
2054*5f7ddb14SDimitry Andric   template <class T>
AddListField(const char * label,T default_field)2055*5f7ddb14SDimitry Andric   ListFieldDelegate<T> *AddListField(const char *label, T default_field) {
2056*5f7ddb14SDimitry Andric     ListFieldDelegate<T> *delegate =
2057*5f7ddb14SDimitry Andric         new ListFieldDelegate<T>(label, default_field);
2058*5f7ddb14SDimitry Andric     m_fields.push_back(FieldDelegateUP(delegate));
2059*5f7ddb14SDimitry Andric     return delegate;
2060*5f7ddb14SDimitry Andric   }
2061*5f7ddb14SDimitry Andric 
2062*5f7ddb14SDimitry Andric   // Factory methods for adding actions.
2063*5f7ddb14SDimitry Andric 
AddAction(const char * label,std::function<void (Window &)> action)2064*5f7ddb14SDimitry Andric   void AddAction(const char *label, std::function<void(Window &)> action) {
2065*5f7ddb14SDimitry Andric     m_actions.push_back(FormAction(label, action));
2066*5f7ddb14SDimitry Andric   }
2067*5f7ddb14SDimitry Andric 
2068*5f7ddb14SDimitry Andric protected:
2069*5f7ddb14SDimitry Andric   std::vector<FieldDelegateUP> m_fields;
2070*5f7ddb14SDimitry Andric   std::vector<FormAction> m_actions;
2071*5f7ddb14SDimitry Andric   // Optional error message. If empty, form is considered to have no error.
2072*5f7ddb14SDimitry Andric   std::string m_error;
2073*5f7ddb14SDimitry Andric };
2074*5f7ddb14SDimitry Andric 
2075*5f7ddb14SDimitry Andric typedef std::shared_ptr<FormDelegate> FormDelegateSP;
2076*5f7ddb14SDimitry Andric 
2077*5f7ddb14SDimitry Andric class FormWindowDelegate : public WindowDelegate {
2078*5f7ddb14SDimitry Andric public:
FormWindowDelegate(FormDelegateSP & delegate_sp)2079*5f7ddb14SDimitry Andric   FormWindowDelegate(FormDelegateSP &delegate_sp)
2080*5f7ddb14SDimitry Andric       : m_delegate_sp(delegate_sp), m_selection_index(0),
2081*5f7ddb14SDimitry Andric         m_first_visible_line(0) {
2082*5f7ddb14SDimitry Andric     assert(m_delegate_sp->GetNumberOfActions() > 0);
2083*5f7ddb14SDimitry Andric     if (m_delegate_sp->GetNumberOfFields() > 0)
2084*5f7ddb14SDimitry Andric       m_selection_type = SelectionType::Field;
2085*5f7ddb14SDimitry Andric     else
2086*5f7ddb14SDimitry Andric       m_selection_type = SelectionType::Action;
2087*5f7ddb14SDimitry Andric   }
2088*5f7ddb14SDimitry Andric 
2089*5f7ddb14SDimitry Andric   // Signify which element is selected. If a field or an action is selected,
2090*5f7ddb14SDimitry Andric   // then m_selection_index signifies the particular field or action that is
2091*5f7ddb14SDimitry Andric   // selected.
2092*5f7ddb14SDimitry Andric   enum class SelectionType { Field, Action };
2093*5f7ddb14SDimitry Andric 
2094*5f7ddb14SDimitry Andric   // A form window is padded by one character from all sides. First, if an error
2095*5f7ddb14SDimitry Andric   // message exists, it is drawn followed by a separator. Then one or more
2096*5f7ddb14SDimitry Andric   // fields are drawn. Finally, all available actions are drawn on a single
2097*5f7ddb14SDimitry Andric   // line.
2098*5f7ddb14SDimitry Andric   //
2099*5f7ddb14SDimitry Andric   // ___<Form Name>_________________________________________________
2100*5f7ddb14SDimitry Andric   // |                                                             |
2101*5f7ddb14SDimitry Andric   // | - Error message if it exists.                               |
2102*5f7ddb14SDimitry Andric   // |-------------------------------------------------------------|
2103*5f7ddb14SDimitry Andric   // | Form elements here.                                         |
2104*5f7ddb14SDimitry Andric   // |                       Form actions here.                    |
2105*5f7ddb14SDimitry Andric   // |                                                             |
2106*5f7ddb14SDimitry Andric   // |______________________________________[Press Esc to cancel]__|
2107*5f7ddb14SDimitry Andric   //
2108*5f7ddb14SDimitry Andric 
2109*5f7ddb14SDimitry Andric   // One line for the error and another for the horizontal line.
GetErrorHeight()2110*5f7ddb14SDimitry Andric   int GetErrorHeight() {
2111*5f7ddb14SDimitry Andric     if (m_delegate_sp->HasError())
2112*5f7ddb14SDimitry Andric       return 2;
2113*5f7ddb14SDimitry Andric     return 0;
2114*5f7ddb14SDimitry Andric   }
2115*5f7ddb14SDimitry Andric 
2116*5f7ddb14SDimitry Andric   // Actions span a single line.
GetActionsHeight()2117*5f7ddb14SDimitry Andric   int GetActionsHeight() {
2118*5f7ddb14SDimitry Andric     if (m_delegate_sp->GetNumberOfActions() > 0)
2119*5f7ddb14SDimitry Andric       return 1;
2120*5f7ddb14SDimitry Andric     return 0;
2121*5f7ddb14SDimitry Andric   }
2122*5f7ddb14SDimitry Andric 
2123*5f7ddb14SDimitry Andric   // Get the total number of needed lines to draw the contents.
GetContentHeight()2124*5f7ddb14SDimitry Andric   int GetContentHeight() {
2125*5f7ddb14SDimitry Andric     int height = 0;
2126*5f7ddb14SDimitry Andric     height += GetErrorHeight();
2127*5f7ddb14SDimitry Andric     for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2128*5f7ddb14SDimitry Andric       if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2129*5f7ddb14SDimitry Andric         continue;
2130*5f7ddb14SDimitry Andric       height += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2131*5f7ddb14SDimitry Andric     }
2132*5f7ddb14SDimitry Andric     height += GetActionsHeight();
2133*5f7ddb14SDimitry Andric     return height;
2134*5f7ddb14SDimitry Andric   }
2135*5f7ddb14SDimitry Andric 
GetScrollContext()2136*5f7ddb14SDimitry Andric   ScrollContext GetScrollContext() {
2137*5f7ddb14SDimitry Andric     if (m_selection_type == SelectionType::Action)
2138*5f7ddb14SDimitry Andric       return ScrollContext(GetContentHeight() - 1);
2139*5f7ddb14SDimitry Andric 
2140*5f7ddb14SDimitry Andric     FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2141*5f7ddb14SDimitry Andric     ScrollContext context = field->FieldDelegateGetScrollContext();
2142*5f7ddb14SDimitry Andric 
2143*5f7ddb14SDimitry Andric     int offset = GetErrorHeight();
2144*5f7ddb14SDimitry Andric     for (int i = 0; i < m_selection_index; i++) {
2145*5f7ddb14SDimitry Andric       if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2146*5f7ddb14SDimitry Andric         continue;
2147*5f7ddb14SDimitry Andric       offset += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2148*5f7ddb14SDimitry Andric     }
2149*5f7ddb14SDimitry Andric     context.Offset(offset);
2150*5f7ddb14SDimitry Andric 
2151*5f7ddb14SDimitry Andric     // If the context is touching the error, include the error in the context as
2152*5f7ddb14SDimitry Andric     // well.
2153*5f7ddb14SDimitry Andric     if (context.start == GetErrorHeight())
2154*5f7ddb14SDimitry Andric       context.start = 0;
2155*5f7ddb14SDimitry Andric 
2156*5f7ddb14SDimitry Andric     return context;
2157*5f7ddb14SDimitry Andric   }
2158*5f7ddb14SDimitry Andric 
UpdateScrolling(DerivedWindow & surface)2159*5f7ddb14SDimitry Andric   void UpdateScrolling(DerivedWindow &surface) {
2160*5f7ddb14SDimitry Andric     ScrollContext context = GetScrollContext();
2161*5f7ddb14SDimitry Andric     int content_height = GetContentHeight();
2162*5f7ddb14SDimitry Andric     int surface_height = surface.GetHeight();
2163*5f7ddb14SDimitry Andric     int visible_height = std::min(content_height, surface_height);
2164*5f7ddb14SDimitry Andric     int last_visible_line = m_first_visible_line + visible_height - 1;
2165*5f7ddb14SDimitry Andric 
2166*5f7ddb14SDimitry Andric     // If the last visible line is bigger than the content, then it is invalid
2167*5f7ddb14SDimitry Andric     // and needs to be set to the last line in the content. This can happen when
2168*5f7ddb14SDimitry Andric     // a field has shrunk in height.
2169*5f7ddb14SDimitry Andric     if (last_visible_line > content_height - 1) {
2170*5f7ddb14SDimitry Andric       m_first_visible_line = content_height - visible_height;
2171*5f7ddb14SDimitry Andric     }
2172*5f7ddb14SDimitry Andric 
2173*5f7ddb14SDimitry Andric     if (context.start < m_first_visible_line) {
2174*5f7ddb14SDimitry Andric       m_first_visible_line = context.start;
2175*5f7ddb14SDimitry Andric       return;
2176*5f7ddb14SDimitry Andric     }
2177*5f7ddb14SDimitry Andric 
2178*5f7ddb14SDimitry Andric     if (context.end > last_visible_line) {
2179*5f7ddb14SDimitry Andric       m_first_visible_line = context.end - visible_height + 1;
2180*5f7ddb14SDimitry Andric     }
2181*5f7ddb14SDimitry Andric   }
2182*5f7ddb14SDimitry Andric 
DrawError(SubPad & surface)2183*5f7ddb14SDimitry Andric   void DrawError(SubPad &surface) {
2184*5f7ddb14SDimitry Andric     if (!m_delegate_sp->HasError())
2185*5f7ddb14SDimitry Andric       return;
2186*5f7ddb14SDimitry Andric     surface.MoveCursor(0, 0);
2187*5f7ddb14SDimitry Andric     surface.AttributeOn(COLOR_PAIR(RedOnBlack));
2188*5f7ddb14SDimitry Andric     surface.PutChar(ACS_DIAMOND);
2189*5f7ddb14SDimitry Andric     surface.PutChar(' ');
2190*5f7ddb14SDimitry Andric     surface.PutCStringTruncated(1, m_delegate_sp->GetError().c_str());
2191*5f7ddb14SDimitry Andric     surface.AttributeOff(COLOR_PAIR(RedOnBlack));
2192*5f7ddb14SDimitry Andric 
2193*5f7ddb14SDimitry Andric     surface.MoveCursor(0, 1);
2194*5f7ddb14SDimitry Andric     surface.HorizontalLine(surface.GetWidth());
2195*5f7ddb14SDimitry Andric   }
2196*5f7ddb14SDimitry Andric 
DrawFields(SubPad & surface)2197*5f7ddb14SDimitry Andric   void DrawFields(SubPad &surface) {
2198*5f7ddb14SDimitry Andric     int line = 0;
2199*5f7ddb14SDimitry Andric     int width = surface.GetWidth();
2200*5f7ddb14SDimitry Andric     bool a_field_is_selected = m_selection_type == SelectionType::Field;
2201*5f7ddb14SDimitry Andric     for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2202*5f7ddb14SDimitry Andric       FieldDelegate *field = m_delegate_sp->GetField(i);
2203*5f7ddb14SDimitry Andric       if (!field->FieldDelegateIsVisible())
2204*5f7ddb14SDimitry Andric         continue;
2205*5f7ddb14SDimitry Andric       bool is_field_selected = a_field_is_selected && m_selection_index == i;
2206*5f7ddb14SDimitry Andric       int height = field->FieldDelegateGetHeight();
2207*5f7ddb14SDimitry Andric       Rect bounds = Rect(Point(0, line), Size(width, height));
2208*5f7ddb14SDimitry Andric       SubPad field_surface = SubPad(surface, bounds);
2209*5f7ddb14SDimitry Andric       field->FieldDelegateDraw(field_surface, is_field_selected);
2210*5f7ddb14SDimitry Andric       line += height;
2211*5f7ddb14SDimitry Andric     }
2212*5f7ddb14SDimitry Andric   }
2213*5f7ddb14SDimitry Andric 
DrawActions(SubPad & surface)2214*5f7ddb14SDimitry Andric   void DrawActions(SubPad &surface) {
2215*5f7ddb14SDimitry Andric     int number_of_actions = m_delegate_sp->GetNumberOfActions();
2216*5f7ddb14SDimitry Andric     int width = surface.GetWidth() / number_of_actions;
2217*5f7ddb14SDimitry Andric     bool an_action_is_selected = m_selection_type == SelectionType::Action;
2218*5f7ddb14SDimitry Andric     int x = 0;
2219*5f7ddb14SDimitry Andric     for (int i = 0; i < number_of_actions; i++) {
2220*5f7ddb14SDimitry Andric       bool is_action_selected = an_action_is_selected && m_selection_index == i;
2221*5f7ddb14SDimitry Andric       FormAction &action = m_delegate_sp->GetAction(i);
2222*5f7ddb14SDimitry Andric       Rect bounds = Rect(Point(x, 0), Size(width, 1));
2223*5f7ddb14SDimitry Andric       SubPad action_surface = SubPad(surface, bounds);
2224*5f7ddb14SDimitry Andric       action.Draw(action_surface, is_action_selected);
2225*5f7ddb14SDimitry Andric       x += width;
2226*5f7ddb14SDimitry Andric     }
2227*5f7ddb14SDimitry Andric   }
2228*5f7ddb14SDimitry Andric 
DrawElements(SubPad & surface)2229*5f7ddb14SDimitry Andric   void DrawElements(SubPad &surface) {
2230*5f7ddb14SDimitry Andric     Rect frame = surface.GetFrame();
2231*5f7ddb14SDimitry Andric     Rect fields_bounds, actions_bounds;
2232*5f7ddb14SDimitry Andric     frame.HorizontalSplit(surface.GetHeight() - GetActionsHeight(),
2233*5f7ddb14SDimitry Andric                           fields_bounds, actions_bounds);
2234*5f7ddb14SDimitry Andric     SubPad fields_surface = SubPad(surface, fields_bounds);
2235*5f7ddb14SDimitry Andric     SubPad actions_surface = SubPad(surface, actions_bounds);
2236*5f7ddb14SDimitry Andric 
2237*5f7ddb14SDimitry Andric     DrawFields(fields_surface);
2238*5f7ddb14SDimitry Andric     DrawActions(actions_surface);
2239*5f7ddb14SDimitry Andric   }
2240*5f7ddb14SDimitry Andric 
2241*5f7ddb14SDimitry Andric   // Contents are first drawn on a pad. Then a subset of that pad is copied to
2242*5f7ddb14SDimitry Andric   // the derived window starting at the first visible line. This essentially
2243*5f7ddb14SDimitry Andric   // provides scrolling functionality.
DrawContent(DerivedWindow & surface)2244*5f7ddb14SDimitry Andric   void DrawContent(DerivedWindow &surface) {
2245*5f7ddb14SDimitry Andric     UpdateScrolling(surface);
2246*5f7ddb14SDimitry Andric 
2247*5f7ddb14SDimitry Andric     int width = surface.GetWidth();
2248*5f7ddb14SDimitry Andric     int height = GetContentHeight();
2249*5f7ddb14SDimitry Andric     Pad pad = Pad(Size(width, height));
2250*5f7ddb14SDimitry Andric 
2251*5f7ddb14SDimitry Andric     Rect frame = pad.GetFrame();
2252*5f7ddb14SDimitry Andric     Rect error_bounds, elements_bounds;
2253*5f7ddb14SDimitry Andric     frame.HorizontalSplit(GetErrorHeight(), error_bounds, elements_bounds);
2254*5f7ddb14SDimitry Andric     SubPad error_surface = SubPad(pad, error_bounds);
2255*5f7ddb14SDimitry Andric     SubPad elements_surface = SubPad(pad, elements_bounds);
2256*5f7ddb14SDimitry Andric 
2257*5f7ddb14SDimitry Andric     DrawError(error_surface);
2258*5f7ddb14SDimitry Andric     DrawElements(elements_surface);
2259*5f7ddb14SDimitry Andric 
2260*5f7ddb14SDimitry Andric     int copy_height = std::min(surface.GetHeight(), pad.GetHeight());
2261*5f7ddb14SDimitry Andric     pad.CopyToSurface(surface, Point(0, m_first_visible_line), Point(),
2262*5f7ddb14SDimitry Andric                       Size(width, copy_height));
2263*5f7ddb14SDimitry Andric   }
2264*5f7ddb14SDimitry Andric 
WindowDelegateDraw(Window & window,bool force)2265*5f7ddb14SDimitry Andric   bool WindowDelegateDraw(Window &window, bool force) override {
2266*5f7ddb14SDimitry Andric     m_delegate_sp->UpdateFieldsVisibility();
2267*5f7ddb14SDimitry Andric 
2268*5f7ddb14SDimitry Andric     window.Erase();
2269*5f7ddb14SDimitry Andric 
2270*5f7ddb14SDimitry Andric     window.DrawTitleBox(m_delegate_sp->GetName().c_str(),
2271*5f7ddb14SDimitry Andric                         "Press Esc to cancel");
2272*5f7ddb14SDimitry Andric 
2273*5f7ddb14SDimitry Andric     Rect content_bounds = window.GetFrame();
2274*5f7ddb14SDimitry Andric     content_bounds.Inset(2, 2);
2275*5f7ddb14SDimitry Andric     DerivedWindow content_surface = DerivedWindow(window, content_bounds);
2276*5f7ddb14SDimitry Andric 
2277*5f7ddb14SDimitry Andric     DrawContent(content_surface);
2278*5f7ddb14SDimitry Andric     return true;
2279*5f7ddb14SDimitry Andric   }
2280*5f7ddb14SDimitry Andric 
SkipNextHiddenFields()2281*5f7ddb14SDimitry Andric   void SkipNextHiddenFields() {
2282*5f7ddb14SDimitry Andric     while (true) {
2283*5f7ddb14SDimitry Andric       if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2284*5f7ddb14SDimitry Andric         return;
2285*5f7ddb14SDimitry Andric 
2286*5f7ddb14SDimitry Andric       if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2287*5f7ddb14SDimitry Andric         m_selection_type = SelectionType::Action;
2288*5f7ddb14SDimitry Andric         m_selection_index = 0;
2289*5f7ddb14SDimitry Andric         return;
2290*5f7ddb14SDimitry Andric       }
2291*5f7ddb14SDimitry Andric 
2292*5f7ddb14SDimitry Andric       m_selection_index++;
2293*5f7ddb14SDimitry Andric     }
2294*5f7ddb14SDimitry Andric   }
2295*5f7ddb14SDimitry Andric 
SelectNext(int key)2296*5f7ddb14SDimitry Andric   HandleCharResult SelectNext(int key) {
2297*5f7ddb14SDimitry Andric     if (m_selection_type == SelectionType::Action) {
2298*5f7ddb14SDimitry Andric       if (m_selection_index < m_delegate_sp->GetNumberOfActions() - 1) {
2299*5f7ddb14SDimitry Andric         m_selection_index++;
2300*5f7ddb14SDimitry Andric         return eKeyHandled;
2301*5f7ddb14SDimitry Andric       }
2302*5f7ddb14SDimitry Andric 
2303*5f7ddb14SDimitry Andric       m_selection_index = 0;
2304*5f7ddb14SDimitry Andric       m_selection_type = SelectionType::Field;
2305*5f7ddb14SDimitry Andric       SkipNextHiddenFields();
2306*5f7ddb14SDimitry Andric       if (m_selection_type == SelectionType::Field) {
2307*5f7ddb14SDimitry Andric         FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2308*5f7ddb14SDimitry Andric         next_field->FieldDelegateSelectFirstElement();
2309*5f7ddb14SDimitry Andric       }
2310*5f7ddb14SDimitry Andric       return eKeyHandled;
2311*5f7ddb14SDimitry Andric     }
2312*5f7ddb14SDimitry Andric 
2313*5f7ddb14SDimitry Andric     FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2314*5f7ddb14SDimitry Andric     if (!field->FieldDelegateOnLastOrOnlyElement()) {
2315*5f7ddb14SDimitry Andric       return field->FieldDelegateHandleChar(key);
2316*5f7ddb14SDimitry Andric     }
2317*5f7ddb14SDimitry Andric 
2318*5f7ddb14SDimitry Andric     field->FieldDelegateExitCallback();
2319*5f7ddb14SDimitry Andric 
2320*5f7ddb14SDimitry Andric     if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2321*5f7ddb14SDimitry Andric       m_selection_type = SelectionType::Action;
2322*5f7ddb14SDimitry Andric       m_selection_index = 0;
2323*5f7ddb14SDimitry Andric       return eKeyHandled;
2324*5f7ddb14SDimitry Andric     }
2325*5f7ddb14SDimitry Andric 
2326*5f7ddb14SDimitry Andric     m_selection_index++;
2327*5f7ddb14SDimitry Andric     SkipNextHiddenFields();
2328*5f7ddb14SDimitry Andric 
2329*5f7ddb14SDimitry Andric     if (m_selection_type == SelectionType::Field) {
2330*5f7ddb14SDimitry Andric       FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2331*5f7ddb14SDimitry Andric       next_field->FieldDelegateSelectFirstElement();
2332*5f7ddb14SDimitry Andric     }
2333*5f7ddb14SDimitry Andric 
2334*5f7ddb14SDimitry Andric     return eKeyHandled;
2335*5f7ddb14SDimitry Andric   }
2336*5f7ddb14SDimitry Andric 
SkipPreviousHiddenFields()2337*5f7ddb14SDimitry Andric   void SkipPreviousHiddenFields() {
2338*5f7ddb14SDimitry Andric     while (true) {
2339*5f7ddb14SDimitry Andric       if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2340*5f7ddb14SDimitry Andric         return;
2341*5f7ddb14SDimitry Andric 
2342*5f7ddb14SDimitry Andric       if (m_selection_index == 0) {
2343*5f7ddb14SDimitry Andric         m_selection_type = SelectionType::Action;
2344*5f7ddb14SDimitry Andric         m_selection_index = 0;
2345*5f7ddb14SDimitry Andric         return;
2346*5f7ddb14SDimitry Andric       }
2347*5f7ddb14SDimitry Andric 
2348*5f7ddb14SDimitry Andric       m_selection_index--;
2349*5f7ddb14SDimitry Andric     }
2350*5f7ddb14SDimitry Andric   }
2351*5f7ddb14SDimitry Andric 
SelectPrevious(int key)2352*5f7ddb14SDimitry Andric   HandleCharResult SelectPrevious(int key) {
2353*5f7ddb14SDimitry Andric     if (m_selection_type == SelectionType::Action) {
2354*5f7ddb14SDimitry Andric       if (m_selection_index > 0) {
2355*5f7ddb14SDimitry Andric         m_selection_index--;
2356*5f7ddb14SDimitry Andric         return eKeyHandled;
2357*5f7ddb14SDimitry Andric       }
2358*5f7ddb14SDimitry Andric       m_selection_index = m_delegate_sp->GetNumberOfFields() - 1;
2359*5f7ddb14SDimitry Andric       m_selection_type = SelectionType::Field;
2360*5f7ddb14SDimitry Andric       SkipPreviousHiddenFields();
2361*5f7ddb14SDimitry Andric       if (m_selection_type == SelectionType::Field) {
2362*5f7ddb14SDimitry Andric         FieldDelegate *previous_field =
2363*5f7ddb14SDimitry Andric             m_delegate_sp->GetField(m_selection_index);
2364*5f7ddb14SDimitry Andric         previous_field->FieldDelegateSelectLastElement();
2365*5f7ddb14SDimitry Andric       }
2366*5f7ddb14SDimitry Andric       return eKeyHandled;
2367*5f7ddb14SDimitry Andric     }
2368*5f7ddb14SDimitry Andric 
2369*5f7ddb14SDimitry Andric     FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2370*5f7ddb14SDimitry Andric     if (!field->FieldDelegateOnFirstOrOnlyElement()) {
2371*5f7ddb14SDimitry Andric       return field->FieldDelegateHandleChar(key);
2372*5f7ddb14SDimitry Andric     }
2373*5f7ddb14SDimitry Andric 
2374*5f7ddb14SDimitry Andric     field->FieldDelegateExitCallback();
2375*5f7ddb14SDimitry Andric 
2376*5f7ddb14SDimitry Andric     if (m_selection_index == 0) {
2377*5f7ddb14SDimitry Andric       m_selection_type = SelectionType::Action;
2378*5f7ddb14SDimitry Andric       m_selection_index = m_delegate_sp->GetNumberOfActions() - 1;
2379*5f7ddb14SDimitry Andric       return eKeyHandled;
2380*5f7ddb14SDimitry Andric     }
2381*5f7ddb14SDimitry Andric 
2382*5f7ddb14SDimitry Andric     m_selection_index--;
2383*5f7ddb14SDimitry Andric     SkipPreviousHiddenFields();
2384*5f7ddb14SDimitry Andric 
2385*5f7ddb14SDimitry Andric     if (m_selection_type == SelectionType::Field) {
2386*5f7ddb14SDimitry Andric       FieldDelegate *previous_field =
2387*5f7ddb14SDimitry Andric           m_delegate_sp->GetField(m_selection_index);
2388*5f7ddb14SDimitry Andric       previous_field->FieldDelegateSelectLastElement();
2389*5f7ddb14SDimitry Andric     }
2390*5f7ddb14SDimitry Andric 
2391*5f7ddb14SDimitry Andric     return eKeyHandled;
2392*5f7ddb14SDimitry Andric   }
2393*5f7ddb14SDimitry Andric 
ExecuteAction(Window & window)2394*5f7ddb14SDimitry Andric   void ExecuteAction(Window &window) {
2395*5f7ddb14SDimitry Andric     FormAction &action = m_delegate_sp->GetAction(m_selection_index);
2396*5f7ddb14SDimitry Andric     action.Execute(window);
2397*5f7ddb14SDimitry Andric     if (m_delegate_sp->HasError()) {
2398*5f7ddb14SDimitry Andric       m_first_visible_line = 0;
2399*5f7ddb14SDimitry Andric       m_selection_index = 0;
2400*5f7ddb14SDimitry Andric       m_selection_type = SelectionType::Field;
2401*5f7ddb14SDimitry Andric     }
2402*5f7ddb14SDimitry Andric   }
2403*5f7ddb14SDimitry Andric 
WindowDelegateHandleChar(Window & window,int key)2404*5f7ddb14SDimitry Andric   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
2405*5f7ddb14SDimitry Andric     switch (key) {
2406*5f7ddb14SDimitry Andric     case '\r':
2407*5f7ddb14SDimitry Andric     case '\n':
2408*5f7ddb14SDimitry Andric     case KEY_ENTER:
2409*5f7ddb14SDimitry Andric       if (m_selection_type == SelectionType::Action) {
2410*5f7ddb14SDimitry Andric         ExecuteAction(window);
2411*5f7ddb14SDimitry Andric         return eKeyHandled;
2412*5f7ddb14SDimitry Andric       }
2413*5f7ddb14SDimitry Andric       break;
2414*5f7ddb14SDimitry Andric     case '\t':
2415*5f7ddb14SDimitry Andric       return SelectNext(key);
2416*5f7ddb14SDimitry Andric     case KEY_SHIFT_TAB:
2417*5f7ddb14SDimitry Andric       return SelectPrevious(key);
2418*5f7ddb14SDimitry Andric     case KEY_ESCAPE:
2419*5f7ddb14SDimitry Andric       window.GetParent()->RemoveSubWindow(&window);
2420*5f7ddb14SDimitry Andric       return eKeyHandled;
2421*5f7ddb14SDimitry Andric     default:
2422*5f7ddb14SDimitry Andric       break;
2423*5f7ddb14SDimitry Andric     }
2424*5f7ddb14SDimitry Andric 
2425*5f7ddb14SDimitry Andric     // If the key wasn't handled and one of the fields is selected, pass the key
2426*5f7ddb14SDimitry Andric     // to that field.
2427*5f7ddb14SDimitry Andric     if (m_selection_type == SelectionType::Field) {
2428*5f7ddb14SDimitry Andric       FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2429*5f7ddb14SDimitry Andric       return field->FieldDelegateHandleChar(key);
2430*5f7ddb14SDimitry Andric     }
2431*5f7ddb14SDimitry Andric 
2432*5f7ddb14SDimitry Andric     return eKeyNotHandled;
2433*5f7ddb14SDimitry Andric   }
2434*5f7ddb14SDimitry Andric 
2435*5f7ddb14SDimitry Andric protected:
2436*5f7ddb14SDimitry Andric   FormDelegateSP m_delegate_sp;
2437*5f7ddb14SDimitry Andric   // The index of the currently selected SelectionType.
2438*5f7ddb14SDimitry Andric   int m_selection_index;
2439*5f7ddb14SDimitry Andric   // See SelectionType class enum.
2440*5f7ddb14SDimitry Andric   SelectionType m_selection_type;
2441*5f7ddb14SDimitry Andric   // The first visible line from the pad.
2442*5f7ddb14SDimitry Andric   int m_first_visible_line;
2443*5f7ddb14SDimitry Andric };
2444*5f7ddb14SDimitry Andric 
2445*5f7ddb14SDimitry Andric ///////////////////////////
2446*5f7ddb14SDimitry Andric // Form Delegate Instances
2447*5f7ddb14SDimitry Andric ///////////////////////////
2448*5f7ddb14SDimitry Andric 
2449*5f7ddb14SDimitry Andric class DetachOrKillProcessFormDelegate : public FormDelegate {
2450*5f7ddb14SDimitry Andric public:
DetachOrKillProcessFormDelegate(Process * process)2451*5f7ddb14SDimitry Andric   DetachOrKillProcessFormDelegate(Process *process) : m_process(process) {
2452*5f7ddb14SDimitry Andric     SetError("There is a running process, either detach or kill it.");
2453*5f7ddb14SDimitry Andric 
2454*5f7ddb14SDimitry Andric     m_keep_stopped_field =
2455*5f7ddb14SDimitry Andric         AddBooleanField("Keep process stopped when detaching.", false);
2456*5f7ddb14SDimitry Andric 
2457*5f7ddb14SDimitry Andric     AddAction("Detach", [this](Window &window) { Detach(window); });
2458*5f7ddb14SDimitry Andric     AddAction("Kill", [this](Window &window) { Kill(window); });
2459*5f7ddb14SDimitry Andric   }
2460*5f7ddb14SDimitry Andric 
GetName()2461*5f7ddb14SDimitry Andric   std::string GetName() override { return "Detach/Kill Process"; }
2462*5f7ddb14SDimitry Andric 
Kill(Window & window)2463*5f7ddb14SDimitry Andric   void Kill(Window &window) {
2464*5f7ddb14SDimitry Andric     Status destroy_status(m_process->Destroy(false));
2465*5f7ddb14SDimitry Andric     if (destroy_status.Fail()) {
2466*5f7ddb14SDimitry Andric       SetError("Failed to kill process.");
2467*5f7ddb14SDimitry Andric       return;
2468*5f7ddb14SDimitry Andric     }
2469*5f7ddb14SDimitry Andric     window.GetParent()->RemoveSubWindow(&window);
2470*5f7ddb14SDimitry Andric   }
2471*5f7ddb14SDimitry Andric 
Detach(Window & window)2472*5f7ddb14SDimitry Andric   void Detach(Window &window) {
2473*5f7ddb14SDimitry Andric     Status detach_status(m_process->Detach(m_keep_stopped_field->GetBoolean()));
2474*5f7ddb14SDimitry Andric     if (detach_status.Fail()) {
2475*5f7ddb14SDimitry Andric       SetError("Failed to detach from process.");
2476*5f7ddb14SDimitry Andric       return;
2477*5f7ddb14SDimitry Andric     }
2478*5f7ddb14SDimitry Andric     window.GetParent()->RemoveSubWindow(&window);
2479*5f7ddb14SDimitry Andric   }
2480*5f7ddb14SDimitry Andric 
2481*5f7ddb14SDimitry Andric protected:
2482*5f7ddb14SDimitry Andric   Process *m_process;
2483*5f7ddb14SDimitry Andric   BooleanFieldDelegate *m_keep_stopped_field;
2484*5f7ddb14SDimitry Andric };
2485*5f7ddb14SDimitry Andric 
2486*5f7ddb14SDimitry Andric class ProcessAttachFormDelegate : public FormDelegate {
2487*5f7ddb14SDimitry Andric public:
ProcessAttachFormDelegate(Debugger & debugger,WindowSP main_window_sp)2488*5f7ddb14SDimitry Andric   ProcessAttachFormDelegate(Debugger &debugger, WindowSP main_window_sp)
2489*5f7ddb14SDimitry Andric       : m_debugger(debugger), m_main_window_sp(main_window_sp) {
2490*5f7ddb14SDimitry Andric     std::vector<std::string> types;
2491*5f7ddb14SDimitry Andric     types.push_back(std::string("Name"));
2492*5f7ddb14SDimitry Andric     types.push_back(std::string("PID"));
2493*5f7ddb14SDimitry Andric     m_type_field = AddChoicesField("Attach By", 2, types);
2494*5f7ddb14SDimitry Andric     m_pid_field = AddIntegerField("PID", 0, true);
2495*5f7ddb14SDimitry Andric     m_name_field =
2496*5f7ddb14SDimitry Andric         AddTextField("Process Name", GetDefaultProcessName().c_str(), true);
2497*5f7ddb14SDimitry Andric     m_continue_field = AddBooleanField("Continue once attached.", false);
2498*5f7ddb14SDimitry Andric     m_wait_for_field = AddBooleanField("Wait for process to launch.", false);
2499*5f7ddb14SDimitry Andric     m_include_existing_field =
2500*5f7ddb14SDimitry Andric         AddBooleanField("Include existing processes.", false);
2501*5f7ddb14SDimitry Andric     m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
2502*5f7ddb14SDimitry Andric     m_plugin_field = AddProcessPluginField();
2503*5f7ddb14SDimitry Andric 
2504*5f7ddb14SDimitry Andric     AddAction("Attach", [this](Window &window) { Attach(window); });
2505*5f7ddb14SDimitry Andric   }
2506*5f7ddb14SDimitry Andric 
GetName()2507*5f7ddb14SDimitry Andric   std::string GetName() override { return "Attach Process"; }
2508*5f7ddb14SDimitry Andric 
UpdateFieldsVisibility()2509*5f7ddb14SDimitry Andric   void UpdateFieldsVisibility() override {
2510*5f7ddb14SDimitry Andric     if (m_type_field->GetChoiceContent() == "Name") {
2511*5f7ddb14SDimitry Andric       m_pid_field->FieldDelegateHide();
2512*5f7ddb14SDimitry Andric       m_name_field->FieldDelegateShow();
2513*5f7ddb14SDimitry Andric       m_wait_for_field->FieldDelegateShow();
2514*5f7ddb14SDimitry Andric       if (m_wait_for_field->GetBoolean())
2515*5f7ddb14SDimitry Andric         m_include_existing_field->FieldDelegateShow();
2516*5f7ddb14SDimitry Andric       else
2517*5f7ddb14SDimitry Andric         m_include_existing_field->FieldDelegateHide();
2518*5f7ddb14SDimitry Andric     } else {
2519*5f7ddb14SDimitry Andric       m_pid_field->FieldDelegateShow();
2520*5f7ddb14SDimitry Andric       m_name_field->FieldDelegateHide();
2521*5f7ddb14SDimitry Andric       m_wait_for_field->FieldDelegateHide();
2522*5f7ddb14SDimitry Andric       m_include_existing_field->FieldDelegateHide();
2523*5f7ddb14SDimitry Andric     }
2524*5f7ddb14SDimitry Andric     if (m_show_advanced_field->GetBoolean())
2525*5f7ddb14SDimitry Andric       m_plugin_field->FieldDelegateShow();
2526*5f7ddb14SDimitry Andric     else
2527*5f7ddb14SDimitry Andric       m_plugin_field->FieldDelegateHide();
2528*5f7ddb14SDimitry Andric   }
2529*5f7ddb14SDimitry Andric 
2530*5f7ddb14SDimitry Andric   // Get the basename of the target's main executable if available, empty string
2531*5f7ddb14SDimitry Andric   // otherwise.
GetDefaultProcessName()2532*5f7ddb14SDimitry Andric   std::string GetDefaultProcessName() {
2533*5f7ddb14SDimitry Andric     Target *target = m_debugger.GetSelectedTarget().get();
2534*5f7ddb14SDimitry Andric     if (target == nullptr)
2535*5f7ddb14SDimitry Andric       return "";
2536*5f7ddb14SDimitry Andric 
2537*5f7ddb14SDimitry Andric     ModuleSP module_sp = target->GetExecutableModule();
2538*5f7ddb14SDimitry Andric     if (!module_sp->IsExecutable())
2539*5f7ddb14SDimitry Andric       return "";
2540*5f7ddb14SDimitry Andric 
2541*5f7ddb14SDimitry Andric     return module_sp->GetFileSpec().GetFilename().AsCString();
2542*5f7ddb14SDimitry Andric   }
2543*5f7ddb14SDimitry Andric 
StopRunningProcess()2544*5f7ddb14SDimitry Andric   bool StopRunningProcess() {
2545*5f7ddb14SDimitry Andric     ExecutionContext exe_ctx =
2546*5f7ddb14SDimitry Andric         m_debugger.GetCommandInterpreter().GetExecutionContext();
2547*5f7ddb14SDimitry Andric 
2548*5f7ddb14SDimitry Andric     if (!exe_ctx.HasProcessScope())
2549*5f7ddb14SDimitry Andric       return false;
2550*5f7ddb14SDimitry Andric 
2551*5f7ddb14SDimitry Andric     Process *process = exe_ctx.GetProcessPtr();
2552*5f7ddb14SDimitry Andric     if (!(process && process->IsAlive()))
2553*5f7ddb14SDimitry Andric       return false;
2554*5f7ddb14SDimitry Andric 
2555*5f7ddb14SDimitry Andric     FormDelegateSP form_delegate_sp =
2556*5f7ddb14SDimitry Andric         FormDelegateSP(new DetachOrKillProcessFormDelegate(process));
2557*5f7ddb14SDimitry Andric     Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);
2558*5f7ddb14SDimitry Andric     WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(
2559*5f7ddb14SDimitry Andric         form_delegate_sp->GetName().c_str(), bounds, true);
2560*5f7ddb14SDimitry Andric     WindowDelegateSP window_delegate_sp =
2561*5f7ddb14SDimitry Andric         WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
2562*5f7ddb14SDimitry Andric     form_window_sp->SetDelegate(window_delegate_sp);
2563*5f7ddb14SDimitry Andric 
2564*5f7ddb14SDimitry Andric     return true;
2565*5f7ddb14SDimitry Andric   }
2566*5f7ddb14SDimitry Andric 
GetTarget()2567*5f7ddb14SDimitry Andric   Target *GetTarget() {
2568*5f7ddb14SDimitry Andric     Target *target = m_debugger.GetSelectedTarget().get();
2569*5f7ddb14SDimitry Andric 
2570*5f7ddb14SDimitry Andric     if (target != nullptr)
2571*5f7ddb14SDimitry Andric       return target;
2572*5f7ddb14SDimitry Andric 
2573*5f7ddb14SDimitry Andric     TargetSP new_target_sp;
2574*5f7ddb14SDimitry Andric     m_debugger.GetTargetList().CreateTarget(
2575*5f7ddb14SDimitry Andric         m_debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp);
2576*5f7ddb14SDimitry Andric 
2577*5f7ddb14SDimitry Andric     target = new_target_sp.get();
2578*5f7ddb14SDimitry Andric 
2579*5f7ddb14SDimitry Andric     if (target == nullptr)
2580*5f7ddb14SDimitry Andric       SetError("Failed to create target.");
2581*5f7ddb14SDimitry Andric 
2582*5f7ddb14SDimitry Andric     m_debugger.GetTargetList().SetSelectedTarget(new_target_sp);
2583*5f7ddb14SDimitry Andric 
2584*5f7ddb14SDimitry Andric     return target;
2585*5f7ddb14SDimitry Andric   }
2586*5f7ddb14SDimitry Andric 
GetAttachInfo()2587*5f7ddb14SDimitry Andric   ProcessAttachInfo GetAttachInfo() {
2588*5f7ddb14SDimitry Andric     ProcessAttachInfo attach_info;
2589*5f7ddb14SDimitry Andric     attach_info.SetContinueOnceAttached(m_continue_field->GetBoolean());
2590*5f7ddb14SDimitry Andric     if (m_type_field->GetChoiceContent() == "Name") {
2591*5f7ddb14SDimitry Andric       attach_info.GetExecutableFile().SetFile(m_name_field->GetText(),
2592*5f7ddb14SDimitry Andric                                               FileSpec::Style::native);
2593*5f7ddb14SDimitry Andric       attach_info.SetWaitForLaunch(m_wait_for_field->GetBoolean());
2594*5f7ddb14SDimitry Andric       if (m_wait_for_field->GetBoolean())
2595*5f7ddb14SDimitry Andric         attach_info.SetIgnoreExisting(!m_include_existing_field->GetBoolean());
2596*5f7ddb14SDimitry Andric     } else {
2597*5f7ddb14SDimitry Andric       attach_info.SetProcessID(m_pid_field->GetInteger());
2598*5f7ddb14SDimitry Andric     }
2599*5f7ddb14SDimitry Andric     attach_info.SetProcessPluginName(m_plugin_field->GetPluginName());
2600*5f7ddb14SDimitry Andric 
2601*5f7ddb14SDimitry Andric     return attach_info;
2602*5f7ddb14SDimitry Andric   }
2603*5f7ddb14SDimitry Andric 
Attach(Window & window)2604*5f7ddb14SDimitry Andric   void Attach(Window &window) {
2605*5f7ddb14SDimitry Andric     ClearError();
2606*5f7ddb14SDimitry Andric 
2607*5f7ddb14SDimitry Andric     bool all_fields_are_valid = CheckFieldsValidity();
2608*5f7ddb14SDimitry Andric     if (!all_fields_are_valid)
2609*5f7ddb14SDimitry Andric       return;
2610*5f7ddb14SDimitry Andric 
2611*5f7ddb14SDimitry Andric     bool process_is_running = StopRunningProcess();
2612*5f7ddb14SDimitry Andric     if (process_is_running)
2613*5f7ddb14SDimitry Andric       return;
2614*5f7ddb14SDimitry Andric 
2615*5f7ddb14SDimitry Andric     Target *target = GetTarget();
2616*5f7ddb14SDimitry Andric     if (HasError())
2617*5f7ddb14SDimitry Andric       return;
2618*5f7ddb14SDimitry Andric 
2619*5f7ddb14SDimitry Andric     StreamString stream;
2620*5f7ddb14SDimitry Andric     ProcessAttachInfo attach_info = GetAttachInfo();
2621*5f7ddb14SDimitry Andric     Status status = target->Attach(attach_info, &stream);
2622*5f7ddb14SDimitry Andric 
2623*5f7ddb14SDimitry Andric     if (status.Fail()) {
2624*5f7ddb14SDimitry Andric       SetError(status.AsCString());
2625*5f7ddb14SDimitry Andric       return;
2626*5f7ddb14SDimitry Andric     }
2627*5f7ddb14SDimitry Andric 
2628*5f7ddb14SDimitry Andric     ProcessSP process_sp(target->GetProcessSP());
2629*5f7ddb14SDimitry Andric     if (!process_sp) {
2630*5f7ddb14SDimitry Andric       SetError("Attached sucessfully but target has no process.");
2631*5f7ddb14SDimitry Andric       return;
2632*5f7ddb14SDimitry Andric     }
2633*5f7ddb14SDimitry Andric 
2634*5f7ddb14SDimitry Andric     if (attach_info.GetContinueOnceAttached())
2635*5f7ddb14SDimitry Andric       process_sp->Resume();
2636*5f7ddb14SDimitry Andric 
2637*5f7ddb14SDimitry Andric     window.GetParent()->RemoveSubWindow(&window);
2638*5f7ddb14SDimitry Andric   }
2639*5f7ddb14SDimitry Andric 
2640*5f7ddb14SDimitry Andric protected:
2641*5f7ddb14SDimitry Andric   Debugger &m_debugger;
2642*5f7ddb14SDimitry Andric   WindowSP m_main_window_sp;
2643*5f7ddb14SDimitry Andric 
2644*5f7ddb14SDimitry Andric   ChoicesFieldDelegate *m_type_field;
2645*5f7ddb14SDimitry Andric   IntegerFieldDelegate *m_pid_field;
2646*5f7ddb14SDimitry Andric   TextFieldDelegate *m_name_field;
2647*5f7ddb14SDimitry Andric   BooleanFieldDelegate *m_continue_field;
2648*5f7ddb14SDimitry Andric   BooleanFieldDelegate *m_wait_for_field;
2649*5f7ddb14SDimitry Andric   BooleanFieldDelegate *m_include_existing_field;
2650*5f7ddb14SDimitry Andric   BooleanFieldDelegate *m_show_advanced_field;
2651*5f7ddb14SDimitry Andric   ProcessPluginFieldDelegate *m_plugin_field;
2652*5f7ddb14SDimitry Andric };
2653*5f7ddb14SDimitry Andric 
2654480093f4SDimitry Andric class MenuDelegate {
2655480093f4SDimitry Andric public:
2656480093f4SDimitry Andric   virtual ~MenuDelegate() = default;
2657480093f4SDimitry Andric 
2658480093f4SDimitry Andric   virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0;
2659480093f4SDimitry Andric };
2660480093f4SDimitry Andric 
2661480093f4SDimitry Andric class Menu : public WindowDelegate {
2662480093f4SDimitry Andric public:
2663480093f4SDimitry Andric   enum class Type { Invalid, Bar, Item, Separator };
2664480093f4SDimitry Andric 
2665480093f4SDimitry Andric   // Menubar or separator constructor
2666480093f4SDimitry Andric   Menu(Type type);
2667480093f4SDimitry Andric 
2668480093f4SDimitry Andric   // Menuitem constructor
2669480093f4SDimitry Andric   Menu(const char *name, const char *key_name, int key_value,
2670480093f4SDimitry Andric        uint64_t identifier);
2671480093f4SDimitry Andric 
2672480093f4SDimitry Andric   ~Menu() override = default;
2673480093f4SDimitry Andric 
GetDelegate() const2674480093f4SDimitry Andric   const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; }
2675480093f4SDimitry Andric 
SetDelegate(const MenuDelegateSP & delegate_sp)2676480093f4SDimitry Andric   void SetDelegate(const MenuDelegateSP &delegate_sp) {
2677480093f4SDimitry Andric     m_delegate_sp = delegate_sp;
2678480093f4SDimitry Andric   }
2679480093f4SDimitry Andric 
2680480093f4SDimitry Andric   void RecalculateNameLengths();
2681480093f4SDimitry Andric 
2682480093f4SDimitry Andric   void AddSubmenu(const MenuSP &menu_sp);
2683480093f4SDimitry Andric 
2684480093f4SDimitry Andric   int DrawAndRunMenu(Window &window);
2685480093f4SDimitry Andric 
2686480093f4SDimitry Andric   void DrawMenuTitle(Window &window, bool highlight);
2687480093f4SDimitry Andric 
2688480093f4SDimitry Andric   bool WindowDelegateDraw(Window &window, bool force) override;
2689480093f4SDimitry Andric 
2690480093f4SDimitry Andric   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
2691480093f4SDimitry Andric 
ActionPrivate(Menu & menu)2692480093f4SDimitry Andric   MenuActionResult ActionPrivate(Menu &menu) {
2693480093f4SDimitry Andric     MenuActionResult result = MenuActionResult::NotHandled;
2694480093f4SDimitry Andric     if (m_delegate_sp) {
2695480093f4SDimitry Andric       result = m_delegate_sp->MenuDelegateAction(menu);
2696480093f4SDimitry Andric       if (result != MenuActionResult::NotHandled)
2697480093f4SDimitry Andric         return result;
2698480093f4SDimitry Andric     } else if (m_parent) {
2699480093f4SDimitry Andric       result = m_parent->ActionPrivate(menu);
2700480093f4SDimitry Andric       if (result != MenuActionResult::NotHandled)
2701480093f4SDimitry Andric         return result;
2702480093f4SDimitry Andric     }
2703480093f4SDimitry Andric     return m_canned_result;
2704480093f4SDimitry Andric   }
2705480093f4SDimitry Andric 
Action()2706480093f4SDimitry Andric   MenuActionResult Action() {
2707480093f4SDimitry Andric     // Call the recursive action so it can try to handle it with the menu
2708480093f4SDimitry Andric     // delegate, and if not, try our parent menu
2709480093f4SDimitry Andric     return ActionPrivate(*this);
2710480093f4SDimitry Andric   }
2711480093f4SDimitry Andric 
SetCannedResult(MenuActionResult result)2712480093f4SDimitry Andric   void SetCannedResult(MenuActionResult result) { m_canned_result = result; }
2713480093f4SDimitry Andric 
GetSubmenus()2714480093f4SDimitry Andric   Menus &GetSubmenus() { return m_submenus; }
2715480093f4SDimitry Andric 
GetSubmenus() const2716480093f4SDimitry Andric   const Menus &GetSubmenus() const { return m_submenus; }
2717480093f4SDimitry Andric 
GetSelectedSubmenuIndex() const2718480093f4SDimitry Andric   int GetSelectedSubmenuIndex() const { return m_selected; }
2719480093f4SDimitry Andric 
SetSelectedSubmenuIndex(int idx)2720480093f4SDimitry Andric   void SetSelectedSubmenuIndex(int idx) { m_selected = idx; }
2721480093f4SDimitry Andric 
GetType() const2722480093f4SDimitry Andric   Type GetType() const { return m_type; }
2723480093f4SDimitry Andric 
GetStartingColumn() const2724480093f4SDimitry Andric   int GetStartingColumn() const { return m_start_col; }
2725480093f4SDimitry Andric 
SetStartingColumn(int col)2726480093f4SDimitry Andric   void SetStartingColumn(int col) { m_start_col = col; }
2727480093f4SDimitry Andric 
GetKeyValue() const2728480093f4SDimitry Andric   int GetKeyValue() const { return m_key_value; }
2729480093f4SDimitry Andric 
GetName()2730480093f4SDimitry Andric   std::string &GetName() { return m_name; }
2731480093f4SDimitry Andric 
GetDrawWidth() const2732480093f4SDimitry Andric   int GetDrawWidth() const {
2733480093f4SDimitry Andric     return m_max_submenu_name_length + m_max_submenu_key_name_length + 8;
2734480093f4SDimitry Andric   }
2735480093f4SDimitry Andric 
GetIdentifier() const2736480093f4SDimitry Andric   uint64_t GetIdentifier() const { return m_identifier; }
2737480093f4SDimitry Andric 
SetIdentifier(uint64_t identifier)2738480093f4SDimitry Andric   void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
2739480093f4SDimitry Andric 
2740480093f4SDimitry Andric protected:
2741480093f4SDimitry Andric   std::string m_name;
2742480093f4SDimitry Andric   std::string m_key_name;
2743480093f4SDimitry Andric   uint64_t m_identifier;
2744480093f4SDimitry Andric   Type m_type;
2745480093f4SDimitry Andric   int m_key_value;
2746480093f4SDimitry Andric   int m_start_col;
2747480093f4SDimitry Andric   int m_max_submenu_name_length;
2748480093f4SDimitry Andric   int m_max_submenu_key_name_length;
2749480093f4SDimitry Andric   int m_selected;
2750480093f4SDimitry Andric   Menu *m_parent;
2751480093f4SDimitry Andric   Menus m_submenus;
2752480093f4SDimitry Andric   WindowSP m_menu_window_sp;
2753480093f4SDimitry Andric   MenuActionResult m_canned_result;
2754480093f4SDimitry Andric   MenuDelegateSP m_delegate_sp;
2755480093f4SDimitry Andric };
2756480093f4SDimitry Andric 
2757480093f4SDimitry Andric // Menubar or separator constructor
Menu(Type type)2758480093f4SDimitry Andric Menu::Menu(Type type)
2759480093f4SDimitry Andric     : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0),
2760480093f4SDimitry Andric       m_start_col(0), m_max_submenu_name_length(0),
2761480093f4SDimitry Andric       m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
2762480093f4SDimitry Andric       m_submenus(), m_canned_result(MenuActionResult::NotHandled),
2763480093f4SDimitry Andric       m_delegate_sp() {}
2764480093f4SDimitry Andric 
2765480093f4SDimitry Andric // Menuitem constructor
Menu(const char * name,const char * key_name,int key_value,uint64_t identifier)2766480093f4SDimitry Andric Menu::Menu(const char *name, const char *key_name, int key_value,
2767480093f4SDimitry Andric            uint64_t identifier)
2768480093f4SDimitry Andric     : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid),
2769480093f4SDimitry Andric       m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0),
2770480093f4SDimitry Andric       m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
2771480093f4SDimitry Andric       m_submenus(), m_canned_result(MenuActionResult::NotHandled),
2772480093f4SDimitry Andric       m_delegate_sp() {
2773480093f4SDimitry Andric   if (name && name[0]) {
2774480093f4SDimitry Andric     m_name = name;
2775480093f4SDimitry Andric     m_type = Type::Item;
2776480093f4SDimitry Andric     if (key_name && key_name[0])
2777480093f4SDimitry Andric       m_key_name = key_name;
2778480093f4SDimitry Andric   } else {
2779480093f4SDimitry Andric     m_type = Type::Separator;
2780480093f4SDimitry Andric   }
2781480093f4SDimitry Andric }
2782480093f4SDimitry Andric 
RecalculateNameLengths()2783480093f4SDimitry Andric void Menu::RecalculateNameLengths() {
2784480093f4SDimitry Andric   m_max_submenu_name_length = 0;
2785480093f4SDimitry Andric   m_max_submenu_key_name_length = 0;
2786480093f4SDimitry Andric   Menus &submenus = GetSubmenus();
2787480093f4SDimitry Andric   const size_t num_submenus = submenus.size();
2788480093f4SDimitry Andric   for (size_t i = 0; i < num_submenus; ++i) {
2789480093f4SDimitry Andric     Menu *submenu = submenus[i].get();
2790480093f4SDimitry Andric     if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size())
2791480093f4SDimitry Andric       m_max_submenu_name_length = submenu->m_name.size();
2792480093f4SDimitry Andric     if (static_cast<size_t>(m_max_submenu_key_name_length) <
2793480093f4SDimitry Andric         submenu->m_key_name.size())
2794480093f4SDimitry Andric       m_max_submenu_key_name_length = submenu->m_key_name.size();
2795480093f4SDimitry Andric   }
2796480093f4SDimitry Andric }
2797480093f4SDimitry Andric 
AddSubmenu(const MenuSP & menu_sp)2798480093f4SDimitry Andric void Menu::AddSubmenu(const MenuSP &menu_sp) {
2799480093f4SDimitry Andric   menu_sp->m_parent = this;
2800480093f4SDimitry Andric   if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size())
2801480093f4SDimitry Andric     m_max_submenu_name_length = menu_sp->m_name.size();
2802480093f4SDimitry Andric   if (static_cast<size_t>(m_max_submenu_key_name_length) <
2803480093f4SDimitry Andric       menu_sp->m_key_name.size())
2804480093f4SDimitry Andric     m_max_submenu_key_name_length = menu_sp->m_key_name.size();
2805480093f4SDimitry Andric   m_submenus.push_back(menu_sp);
2806480093f4SDimitry Andric }
2807480093f4SDimitry Andric 
DrawMenuTitle(Window & window,bool highlight)2808480093f4SDimitry Andric void Menu::DrawMenuTitle(Window &window, bool highlight) {
2809480093f4SDimitry Andric   if (m_type == Type::Separator) {
2810480093f4SDimitry Andric     window.MoveCursor(0, window.GetCursorY());
2811480093f4SDimitry Andric     window.PutChar(ACS_LTEE);
2812480093f4SDimitry Andric     int width = window.GetWidth();
2813480093f4SDimitry Andric     if (width > 2) {
2814480093f4SDimitry Andric       width -= 2;
2815480093f4SDimitry Andric       for (int i = 0; i < width; ++i)
2816480093f4SDimitry Andric         window.PutChar(ACS_HLINE);
2817480093f4SDimitry Andric     }
2818480093f4SDimitry Andric     window.PutChar(ACS_RTEE);
2819480093f4SDimitry Andric   } else {
2820480093f4SDimitry Andric     const int shortcut_key = m_key_value;
2821480093f4SDimitry Andric     bool underlined_shortcut = false;
2822af732203SDimitry Andric     const attr_t highlight_attr = A_REVERSE;
2823480093f4SDimitry Andric     if (highlight)
2824af732203SDimitry Andric       window.AttributeOn(highlight_attr);
28255ffd83dbSDimitry Andric     if (llvm::isPrint(shortcut_key)) {
2826480093f4SDimitry Andric       size_t lower_pos = m_name.find(tolower(shortcut_key));
2827480093f4SDimitry Andric       size_t upper_pos = m_name.find(toupper(shortcut_key));
2828480093f4SDimitry Andric       const char *name = m_name.c_str();
2829480093f4SDimitry Andric       size_t pos = std::min<size_t>(lower_pos, upper_pos);
2830480093f4SDimitry Andric       if (pos != std::string::npos) {
2831480093f4SDimitry Andric         underlined_shortcut = true;
2832480093f4SDimitry Andric         if (pos > 0) {
2833480093f4SDimitry Andric           window.PutCString(name, pos);
2834480093f4SDimitry Andric           name += pos;
2835480093f4SDimitry Andric         }
2836480093f4SDimitry Andric         const attr_t shortcut_attr = A_UNDERLINE | A_BOLD;
2837480093f4SDimitry Andric         window.AttributeOn(shortcut_attr);
2838480093f4SDimitry Andric         window.PutChar(name[0]);
2839480093f4SDimitry Andric         window.AttributeOff(shortcut_attr);
2840480093f4SDimitry Andric         name++;
2841480093f4SDimitry Andric         if (name[0])
2842480093f4SDimitry Andric           window.PutCString(name);
2843480093f4SDimitry Andric       }
2844480093f4SDimitry Andric     }
2845480093f4SDimitry Andric 
2846480093f4SDimitry Andric     if (!underlined_shortcut) {
2847480093f4SDimitry Andric       window.PutCString(m_name.c_str());
2848480093f4SDimitry Andric     }
2849480093f4SDimitry Andric 
2850480093f4SDimitry Andric     if (highlight)
2851af732203SDimitry Andric       window.AttributeOff(highlight_attr);
2852480093f4SDimitry Andric 
2853480093f4SDimitry Andric     if (m_key_name.empty()) {
28545ffd83dbSDimitry Andric       if (!underlined_shortcut && llvm::isPrint(m_key_value)) {
2855af732203SDimitry Andric         window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
2856480093f4SDimitry Andric         window.Printf(" (%c)", m_key_value);
2857af732203SDimitry Andric         window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
2858480093f4SDimitry Andric       }
2859480093f4SDimitry Andric     } else {
2860af732203SDimitry Andric       window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
2861480093f4SDimitry Andric       window.Printf(" (%s)", m_key_name.c_str());
2862af732203SDimitry Andric       window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
2863480093f4SDimitry Andric     }
2864480093f4SDimitry Andric   }
2865480093f4SDimitry Andric }
2866480093f4SDimitry Andric 
WindowDelegateDraw(Window & window,bool force)2867480093f4SDimitry Andric bool Menu::WindowDelegateDraw(Window &window, bool force) {
2868480093f4SDimitry Andric   Menus &submenus = GetSubmenus();
2869480093f4SDimitry Andric   const size_t num_submenus = submenus.size();
2870480093f4SDimitry Andric   const int selected_idx = GetSelectedSubmenuIndex();
2871480093f4SDimitry Andric   Menu::Type menu_type = GetType();
2872480093f4SDimitry Andric   switch (menu_type) {
2873480093f4SDimitry Andric   case Menu::Type::Bar: {
2874af732203SDimitry Andric     window.SetBackground(BlackOnWhite);
2875480093f4SDimitry Andric     window.MoveCursor(0, 0);
2876480093f4SDimitry Andric     for (size_t i = 0; i < num_submenus; ++i) {
2877480093f4SDimitry Andric       Menu *menu = submenus[i].get();
2878480093f4SDimitry Andric       if (i > 0)
2879480093f4SDimitry Andric         window.PutChar(' ');
2880480093f4SDimitry Andric       menu->SetStartingColumn(window.GetCursorX());
2881480093f4SDimitry Andric       window.PutCString("| ");
2882480093f4SDimitry Andric       menu->DrawMenuTitle(window, false);
2883480093f4SDimitry Andric     }
2884480093f4SDimitry Andric     window.PutCString(" |");
2885480093f4SDimitry Andric   } break;
2886480093f4SDimitry Andric 
2887480093f4SDimitry Andric   case Menu::Type::Item: {
2888480093f4SDimitry Andric     int y = 1;
2889480093f4SDimitry Andric     int x = 3;
2890480093f4SDimitry Andric     // Draw the menu
2891480093f4SDimitry Andric     int cursor_x = 0;
2892480093f4SDimitry Andric     int cursor_y = 0;
2893480093f4SDimitry Andric     window.Erase();
2894af732203SDimitry Andric     window.SetBackground(BlackOnWhite);
2895480093f4SDimitry Andric     window.Box();
2896480093f4SDimitry Andric     for (size_t i = 0; i < num_submenus; ++i) {
2897480093f4SDimitry Andric       const bool is_selected = (i == static_cast<size_t>(selected_idx));
2898480093f4SDimitry Andric       window.MoveCursor(x, y + i);
2899480093f4SDimitry Andric       if (is_selected) {
2900480093f4SDimitry Andric         // Remember where we want the cursor to be
2901480093f4SDimitry Andric         cursor_x = x - 1;
2902480093f4SDimitry Andric         cursor_y = y + i;
2903480093f4SDimitry Andric       }
2904480093f4SDimitry Andric       submenus[i]->DrawMenuTitle(window, is_selected);
2905480093f4SDimitry Andric     }
2906480093f4SDimitry Andric     window.MoveCursor(cursor_x, cursor_y);
2907480093f4SDimitry Andric   } break;
2908480093f4SDimitry Andric 
2909480093f4SDimitry Andric   default:
2910480093f4SDimitry Andric   case Menu::Type::Separator:
2911480093f4SDimitry Andric     break;
2912480093f4SDimitry Andric   }
2913480093f4SDimitry Andric   return true; // Drawing handled...
2914480093f4SDimitry Andric }
2915480093f4SDimitry Andric 
WindowDelegateHandleChar(Window & window,int key)2916480093f4SDimitry Andric HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) {
2917480093f4SDimitry Andric   HandleCharResult result = eKeyNotHandled;
2918480093f4SDimitry Andric 
2919480093f4SDimitry Andric   Menus &submenus = GetSubmenus();
2920480093f4SDimitry Andric   const size_t num_submenus = submenus.size();
2921480093f4SDimitry Andric   const int selected_idx = GetSelectedSubmenuIndex();
2922480093f4SDimitry Andric   Menu::Type menu_type = GetType();
2923480093f4SDimitry Andric   if (menu_type == Menu::Type::Bar) {
2924480093f4SDimitry Andric     MenuSP run_menu_sp;
2925480093f4SDimitry Andric     switch (key) {
2926480093f4SDimitry Andric     case KEY_DOWN:
2927480093f4SDimitry Andric     case KEY_UP:
2928480093f4SDimitry Andric       // Show last menu or first menu
2929480093f4SDimitry Andric       if (selected_idx < static_cast<int>(num_submenus))
2930480093f4SDimitry Andric         run_menu_sp = submenus[selected_idx];
2931480093f4SDimitry Andric       else if (!submenus.empty())
2932480093f4SDimitry Andric         run_menu_sp = submenus.front();
2933480093f4SDimitry Andric       result = eKeyHandled;
2934480093f4SDimitry Andric       break;
2935480093f4SDimitry Andric 
2936480093f4SDimitry Andric     case KEY_RIGHT:
2937480093f4SDimitry Andric       ++m_selected;
2938480093f4SDimitry Andric       if (m_selected >= static_cast<int>(num_submenus))
2939480093f4SDimitry Andric         m_selected = 0;
2940480093f4SDimitry Andric       if (m_selected < static_cast<int>(num_submenus))
2941480093f4SDimitry Andric         run_menu_sp = submenus[m_selected];
2942480093f4SDimitry Andric       else if (!submenus.empty())
2943480093f4SDimitry Andric         run_menu_sp = submenus.front();
2944480093f4SDimitry Andric       result = eKeyHandled;
2945480093f4SDimitry Andric       break;
2946480093f4SDimitry Andric 
2947480093f4SDimitry Andric     case KEY_LEFT:
2948480093f4SDimitry Andric       --m_selected;
2949480093f4SDimitry Andric       if (m_selected < 0)
2950480093f4SDimitry Andric         m_selected = num_submenus - 1;
2951480093f4SDimitry Andric       if (m_selected < static_cast<int>(num_submenus))
2952480093f4SDimitry Andric         run_menu_sp = submenus[m_selected];
2953480093f4SDimitry Andric       else if (!submenus.empty())
2954480093f4SDimitry Andric         run_menu_sp = submenus.front();
2955480093f4SDimitry Andric       result = eKeyHandled;
2956480093f4SDimitry Andric       break;
2957480093f4SDimitry Andric 
2958480093f4SDimitry Andric     default:
2959480093f4SDimitry Andric       for (size_t i = 0; i < num_submenus; ++i) {
2960480093f4SDimitry Andric         if (submenus[i]->GetKeyValue() == key) {
2961480093f4SDimitry Andric           SetSelectedSubmenuIndex(i);
2962480093f4SDimitry Andric           run_menu_sp = submenus[i];
2963480093f4SDimitry Andric           result = eKeyHandled;
2964480093f4SDimitry Andric           break;
2965480093f4SDimitry Andric         }
2966480093f4SDimitry Andric       }
2967480093f4SDimitry Andric       break;
2968480093f4SDimitry Andric     }
2969480093f4SDimitry Andric 
2970480093f4SDimitry Andric     if (run_menu_sp) {
2971480093f4SDimitry Andric       // Run the action on this menu in case we need to populate the menu with
2972480093f4SDimitry Andric       // dynamic content and also in case check marks, and any other menu
2973480093f4SDimitry Andric       // decorations need to be calculated
2974480093f4SDimitry Andric       if (run_menu_sp->Action() == MenuActionResult::Quit)
2975480093f4SDimitry Andric         return eQuitApplication;
2976480093f4SDimitry Andric 
2977480093f4SDimitry Andric       Rect menu_bounds;
2978480093f4SDimitry Andric       menu_bounds.origin.x = run_menu_sp->GetStartingColumn();
2979480093f4SDimitry Andric       menu_bounds.origin.y = 1;
2980480093f4SDimitry Andric       menu_bounds.size.width = run_menu_sp->GetDrawWidth();
2981480093f4SDimitry Andric       menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2;
2982480093f4SDimitry Andric       if (m_menu_window_sp)
2983480093f4SDimitry Andric         window.GetParent()->RemoveSubWindow(m_menu_window_sp.get());
2984480093f4SDimitry Andric 
2985480093f4SDimitry Andric       m_menu_window_sp = window.GetParent()->CreateSubWindow(
2986480093f4SDimitry Andric           run_menu_sp->GetName().c_str(), menu_bounds, true);
2987480093f4SDimitry Andric       m_menu_window_sp->SetDelegate(run_menu_sp);
2988480093f4SDimitry Andric     }
2989480093f4SDimitry Andric   } else if (menu_type == Menu::Type::Item) {
2990480093f4SDimitry Andric     switch (key) {
2991480093f4SDimitry Andric     case KEY_DOWN:
2992480093f4SDimitry Andric       if (m_submenus.size() > 1) {
2993480093f4SDimitry Andric         const int start_select = m_selected;
2994480093f4SDimitry Andric         while (++m_selected != start_select) {
2995480093f4SDimitry Andric           if (static_cast<size_t>(m_selected) >= num_submenus)
2996480093f4SDimitry Andric             m_selected = 0;
2997480093f4SDimitry Andric           if (m_submenus[m_selected]->GetType() == Type::Separator)
2998480093f4SDimitry Andric             continue;
2999480093f4SDimitry Andric           else
3000480093f4SDimitry Andric             break;
3001480093f4SDimitry Andric         }
3002480093f4SDimitry Andric         return eKeyHandled;
3003480093f4SDimitry Andric       }
3004480093f4SDimitry Andric       break;
3005480093f4SDimitry Andric 
3006480093f4SDimitry Andric     case KEY_UP:
3007480093f4SDimitry Andric       if (m_submenus.size() > 1) {
3008480093f4SDimitry Andric         const int start_select = m_selected;
3009480093f4SDimitry Andric         while (--m_selected != start_select) {
3010480093f4SDimitry Andric           if (m_selected < static_cast<int>(0))
3011480093f4SDimitry Andric             m_selected = num_submenus - 1;
3012480093f4SDimitry Andric           if (m_submenus[m_selected]->GetType() == Type::Separator)
3013480093f4SDimitry Andric             continue;
3014480093f4SDimitry Andric           else
3015480093f4SDimitry Andric             break;
3016480093f4SDimitry Andric         }
3017480093f4SDimitry Andric         return eKeyHandled;
3018480093f4SDimitry Andric       }
3019480093f4SDimitry Andric       break;
3020480093f4SDimitry Andric 
3021480093f4SDimitry Andric     case KEY_RETURN:
3022480093f4SDimitry Andric       if (static_cast<size_t>(selected_idx) < num_submenus) {
3023480093f4SDimitry Andric         if (submenus[selected_idx]->Action() == MenuActionResult::Quit)
3024480093f4SDimitry Andric           return eQuitApplication;
3025480093f4SDimitry Andric         window.GetParent()->RemoveSubWindow(&window);
3026480093f4SDimitry Andric         return eKeyHandled;
3027480093f4SDimitry Andric       }
3028480093f4SDimitry Andric       break;
3029480093f4SDimitry Andric 
3030480093f4SDimitry Andric     case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in
3031480093f4SDimitry Andric                      // case other chars are entered for escaped sequences
3032480093f4SDimitry Andric       window.GetParent()->RemoveSubWindow(&window);
3033480093f4SDimitry Andric       return eKeyHandled;
3034480093f4SDimitry Andric 
3035480093f4SDimitry Andric     default:
3036480093f4SDimitry Andric       for (size_t i = 0; i < num_submenus; ++i) {
3037480093f4SDimitry Andric         Menu *menu = submenus[i].get();
3038480093f4SDimitry Andric         if (menu->GetKeyValue() == key) {
3039480093f4SDimitry Andric           SetSelectedSubmenuIndex(i);
3040480093f4SDimitry Andric           window.GetParent()->RemoveSubWindow(&window);
3041480093f4SDimitry Andric           if (menu->Action() == MenuActionResult::Quit)
3042480093f4SDimitry Andric             return eQuitApplication;
3043480093f4SDimitry Andric           return eKeyHandled;
3044480093f4SDimitry Andric         }
3045480093f4SDimitry Andric       }
3046480093f4SDimitry Andric       break;
3047480093f4SDimitry Andric     }
3048480093f4SDimitry Andric   } else if (menu_type == Menu::Type::Separator) {
3049480093f4SDimitry Andric   }
3050480093f4SDimitry Andric   return result;
3051480093f4SDimitry Andric }
3052480093f4SDimitry Andric 
3053480093f4SDimitry Andric class Application {
3054480093f4SDimitry Andric public:
Application(FILE * in,FILE * out)3055480093f4SDimitry Andric   Application(FILE *in, FILE *out)
3056480093f4SDimitry Andric       : m_window_sp(), m_screen(nullptr), m_in(in), m_out(out) {}
3057480093f4SDimitry Andric 
~Application()3058480093f4SDimitry Andric   ~Application() {
3059480093f4SDimitry Andric     m_window_delegates.clear();
3060480093f4SDimitry Andric     m_window_sp.reset();
3061480093f4SDimitry Andric     if (m_screen) {
3062480093f4SDimitry Andric       ::delscreen(m_screen);
3063480093f4SDimitry Andric       m_screen = nullptr;
3064480093f4SDimitry Andric     }
3065480093f4SDimitry Andric   }
3066480093f4SDimitry Andric 
Initialize()3067480093f4SDimitry Andric   void Initialize() {
3068480093f4SDimitry Andric     m_screen = ::newterm(nullptr, m_out, m_in);
3069480093f4SDimitry Andric     ::start_color();
3070480093f4SDimitry Andric     ::curs_set(0);
3071480093f4SDimitry Andric     ::noecho();
3072480093f4SDimitry Andric     ::keypad(stdscr, TRUE);
3073480093f4SDimitry Andric   }
3074480093f4SDimitry Andric 
Terminate()3075480093f4SDimitry Andric   void Terminate() { ::endwin(); }
3076480093f4SDimitry Andric 
Run(Debugger & debugger)3077480093f4SDimitry Andric   void Run(Debugger &debugger) {
3078480093f4SDimitry Andric     bool done = false;
3079480093f4SDimitry Andric     int delay_in_tenths_of_a_second = 1;
3080480093f4SDimitry Andric 
3081480093f4SDimitry Andric     // Alas the threading model in curses is a bit lame so we need to resort to
3082480093f4SDimitry Andric     // polling every 0.5 seconds. We could poll for stdin ourselves and then
3083480093f4SDimitry Andric     // pass the keys down but then we need to translate all of the escape
3084480093f4SDimitry Andric     // sequences ourselves. So we resort to polling for input because we need
3085480093f4SDimitry Andric     // to receive async process events while in this loop.
3086480093f4SDimitry Andric 
3087480093f4SDimitry Andric     halfdelay(delay_in_tenths_of_a_second); // Poll using some number of tenths
3088480093f4SDimitry Andric                                             // of seconds seconds when calling
3089480093f4SDimitry Andric                                             // Window::GetChar()
3090480093f4SDimitry Andric 
3091480093f4SDimitry Andric     ListenerSP listener_sp(
3092480093f4SDimitry Andric         Listener::MakeListener("lldb.IOHandler.curses.Application"));
3093480093f4SDimitry Andric     ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass());
3094480093f4SDimitry Andric     debugger.EnableForwardEvents(listener_sp);
3095480093f4SDimitry Andric 
3096af732203SDimitry Andric     m_update_screen = true;
3097480093f4SDimitry Andric #if defined(__APPLE__)
3098480093f4SDimitry Andric     std::deque<int> escape_chars;
3099480093f4SDimitry Andric #endif
3100480093f4SDimitry Andric 
3101480093f4SDimitry Andric     while (!done) {
3102af732203SDimitry Andric       if (m_update_screen) {
3103480093f4SDimitry Andric         m_window_sp->Draw(false);
3104480093f4SDimitry Andric         // All windows should be calling Window::DeferredRefresh() instead of
3105480093f4SDimitry Andric         // Window::Refresh() so we can do a single update and avoid any screen
3106480093f4SDimitry Andric         // blinking
3107480093f4SDimitry Andric         update_panels();
3108480093f4SDimitry Andric 
3109480093f4SDimitry Andric         // Cursor hiding isn't working on MacOSX, so hide it in the top left
3110480093f4SDimitry Andric         // corner
3111480093f4SDimitry Andric         m_window_sp->MoveCursor(0, 0);
3112480093f4SDimitry Andric 
3113480093f4SDimitry Andric         doupdate();
3114af732203SDimitry Andric         m_update_screen = false;
3115480093f4SDimitry Andric       }
3116480093f4SDimitry Andric 
3117480093f4SDimitry Andric #if defined(__APPLE__)
3118480093f4SDimitry Andric       // Terminal.app doesn't map its function keys correctly, F1-F4 default
3119480093f4SDimitry Andric       // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if
3120480093f4SDimitry Andric       // possible
3121480093f4SDimitry Andric       int ch;
3122480093f4SDimitry Andric       if (escape_chars.empty())
3123480093f4SDimitry Andric         ch = m_window_sp->GetChar();
3124480093f4SDimitry Andric       else {
3125480093f4SDimitry Andric         ch = escape_chars.front();
3126480093f4SDimitry Andric         escape_chars.pop_front();
3127480093f4SDimitry Andric       }
3128480093f4SDimitry Andric       if (ch == KEY_ESCAPE) {
3129480093f4SDimitry Andric         int ch2 = m_window_sp->GetChar();
3130480093f4SDimitry Andric         if (ch2 == 'O') {
3131480093f4SDimitry Andric           int ch3 = m_window_sp->GetChar();
3132480093f4SDimitry Andric           switch (ch3) {
3133480093f4SDimitry Andric           case 'P':
3134480093f4SDimitry Andric             ch = KEY_F(1);
3135480093f4SDimitry Andric             break;
3136480093f4SDimitry Andric           case 'Q':
3137480093f4SDimitry Andric             ch = KEY_F(2);
3138480093f4SDimitry Andric             break;
3139480093f4SDimitry Andric           case 'R':
3140480093f4SDimitry Andric             ch = KEY_F(3);
3141480093f4SDimitry Andric             break;
3142480093f4SDimitry Andric           case 'S':
3143480093f4SDimitry Andric             ch = KEY_F(4);
3144480093f4SDimitry Andric             break;
3145480093f4SDimitry Andric           default:
3146480093f4SDimitry Andric             escape_chars.push_back(ch2);
3147480093f4SDimitry Andric             if (ch3 != -1)
3148480093f4SDimitry Andric               escape_chars.push_back(ch3);
3149480093f4SDimitry Andric             break;
3150480093f4SDimitry Andric           }
3151480093f4SDimitry Andric         } else if (ch2 != -1)
3152480093f4SDimitry Andric           escape_chars.push_back(ch2);
3153480093f4SDimitry Andric       }
3154480093f4SDimitry Andric #else
3155480093f4SDimitry Andric       int ch = m_window_sp->GetChar();
3156480093f4SDimitry Andric 
3157480093f4SDimitry Andric #endif
3158480093f4SDimitry Andric       if (ch == -1) {
3159480093f4SDimitry Andric         if (feof(m_in) || ferror(m_in)) {
3160480093f4SDimitry Andric           done = true;
3161480093f4SDimitry Andric         } else {
3162480093f4SDimitry Andric           // Just a timeout from using halfdelay(), check for events
3163480093f4SDimitry Andric           EventSP event_sp;
3164480093f4SDimitry Andric           while (listener_sp->PeekAtNextEvent()) {
3165480093f4SDimitry Andric             listener_sp->GetEvent(event_sp, std::chrono::seconds(0));
3166480093f4SDimitry Andric 
3167480093f4SDimitry Andric             if (event_sp) {
3168480093f4SDimitry Andric               Broadcaster *broadcaster = event_sp->GetBroadcaster();
3169480093f4SDimitry Andric               if (broadcaster) {
3170480093f4SDimitry Andric                 // uint32_t event_type = event_sp->GetType();
3171480093f4SDimitry Andric                 ConstString broadcaster_class(
3172480093f4SDimitry Andric                     broadcaster->GetBroadcasterClass());
3173480093f4SDimitry Andric                 if (broadcaster_class == broadcaster_class_process) {
3174af732203SDimitry Andric                   m_update_screen = true;
3175480093f4SDimitry Andric                   continue; // Don't get any key, just update our view
3176480093f4SDimitry Andric                 }
3177480093f4SDimitry Andric               }
3178480093f4SDimitry Andric             }
3179480093f4SDimitry Andric           }
3180480093f4SDimitry Andric         }
3181480093f4SDimitry Andric       } else {
3182480093f4SDimitry Andric         HandleCharResult key_result = m_window_sp->HandleChar(ch);
3183480093f4SDimitry Andric         switch (key_result) {
3184480093f4SDimitry Andric         case eKeyHandled:
3185af732203SDimitry Andric           m_update_screen = true;
3186480093f4SDimitry Andric           break;
3187480093f4SDimitry Andric         case eKeyNotHandled:
3188af732203SDimitry Andric           if (ch == 12) { // Ctrl+L, force full redraw
3189af732203SDimitry Andric             redrawwin(m_window_sp->get());
3190af732203SDimitry Andric             m_update_screen = true;
3191af732203SDimitry Andric           }
3192480093f4SDimitry Andric           break;
3193480093f4SDimitry Andric         case eQuitApplication:
3194480093f4SDimitry Andric           done = true;
3195480093f4SDimitry Andric           break;
3196480093f4SDimitry Andric         }
3197480093f4SDimitry Andric       }
3198480093f4SDimitry Andric     }
3199480093f4SDimitry Andric 
3200480093f4SDimitry Andric     debugger.CancelForwardEvents(listener_sp);
3201480093f4SDimitry Andric   }
3202480093f4SDimitry Andric 
GetMainWindow()3203480093f4SDimitry Andric   WindowSP &GetMainWindow() {
3204480093f4SDimitry Andric     if (!m_window_sp)
3205480093f4SDimitry Andric       m_window_sp = std::make_shared<Window>("main", stdscr, false);
3206480093f4SDimitry Andric     return m_window_sp;
3207480093f4SDimitry Andric   }
3208480093f4SDimitry Andric 
TerminalSizeChanged()3209af732203SDimitry Andric   void TerminalSizeChanged() {
3210af732203SDimitry Andric     ::endwin();
3211af732203SDimitry Andric     ::refresh();
3212af732203SDimitry Andric     Rect content_bounds = m_window_sp->GetFrame();
3213af732203SDimitry Andric     m_window_sp->SetBounds(content_bounds);
3214af732203SDimitry Andric     if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar"))
3215af732203SDimitry Andric       menubar_window_sp->SetBounds(content_bounds.MakeMenuBar());
3216af732203SDimitry Andric     if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status"))
3217af732203SDimitry Andric       status_window_sp->SetBounds(content_bounds.MakeStatusBar());
3218af732203SDimitry Andric 
3219af732203SDimitry Andric     WindowSP source_window_sp = m_window_sp->FindSubWindow("Source");
3220af732203SDimitry Andric     WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables");
3221af732203SDimitry Andric     WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers");
3222af732203SDimitry Andric     WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads");
3223af732203SDimitry Andric 
3224af732203SDimitry Andric     Rect threads_bounds;
3225af732203SDimitry Andric     Rect source_variables_bounds;
3226af732203SDimitry Andric     content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
3227af732203SDimitry Andric                                            threads_bounds);
3228af732203SDimitry Andric     if (threads_window_sp)
3229af732203SDimitry Andric       threads_window_sp->SetBounds(threads_bounds);
3230af732203SDimitry Andric     else
3231af732203SDimitry Andric       source_variables_bounds = content_bounds;
3232af732203SDimitry Andric 
3233af732203SDimitry Andric     Rect source_bounds;
3234af732203SDimitry Andric     Rect variables_registers_bounds;
3235af732203SDimitry Andric     source_variables_bounds.HorizontalSplitPercentage(
3236af732203SDimitry Andric         0.70, source_bounds, variables_registers_bounds);
3237af732203SDimitry Andric     if (variables_window_sp || registers_window_sp) {
3238af732203SDimitry Andric       if (variables_window_sp && registers_window_sp) {
3239af732203SDimitry Andric         Rect variables_bounds;
3240af732203SDimitry Andric         Rect registers_bounds;
3241af732203SDimitry Andric         variables_registers_bounds.VerticalSplitPercentage(
3242af732203SDimitry Andric             0.50, variables_bounds, registers_bounds);
3243af732203SDimitry Andric         variables_window_sp->SetBounds(variables_bounds);
3244af732203SDimitry Andric         registers_window_sp->SetBounds(registers_bounds);
3245af732203SDimitry Andric       } else if (variables_window_sp) {
3246af732203SDimitry Andric         variables_window_sp->SetBounds(variables_registers_bounds);
3247af732203SDimitry Andric       } else {
3248af732203SDimitry Andric         registers_window_sp->SetBounds(variables_registers_bounds);
3249af732203SDimitry Andric       }
3250af732203SDimitry Andric     } else {
3251af732203SDimitry Andric       source_bounds = source_variables_bounds;
3252af732203SDimitry Andric     }
3253af732203SDimitry Andric 
3254af732203SDimitry Andric     source_window_sp->SetBounds(source_bounds);
3255af732203SDimitry Andric 
3256af732203SDimitry Andric     touchwin(stdscr);
3257af732203SDimitry Andric     redrawwin(m_window_sp->get());
3258af732203SDimitry Andric     m_update_screen = true;
3259af732203SDimitry Andric   }
3260af732203SDimitry Andric 
3261480093f4SDimitry Andric protected:
3262480093f4SDimitry Andric   WindowSP m_window_sp;
3263480093f4SDimitry Andric   WindowDelegates m_window_delegates;
3264480093f4SDimitry Andric   SCREEN *m_screen;
3265480093f4SDimitry Andric   FILE *m_in;
3266480093f4SDimitry Andric   FILE *m_out;
3267af732203SDimitry Andric   bool m_update_screen = false;
3268480093f4SDimitry Andric };
3269480093f4SDimitry Andric 
3270480093f4SDimitry Andric } // namespace curses
3271480093f4SDimitry Andric 
3272480093f4SDimitry Andric using namespace curses;
3273480093f4SDimitry Andric 
3274480093f4SDimitry Andric struct Row {
3275*5f7ddb14SDimitry Andric   ValueObjectUpdater value;
3276480093f4SDimitry Andric   Row *parent;
3277480093f4SDimitry Andric   // The process stop ID when the children were calculated.
3278af732203SDimitry Andric   uint32_t children_stop_id = 0;
3279af732203SDimitry Andric   int row_idx = 0;
3280af732203SDimitry Andric   int x = 1;
3281af732203SDimitry Andric   int y = 1;
3282480093f4SDimitry Andric   bool might_have_children;
3283af732203SDimitry Andric   bool expanded = false;
3284af732203SDimitry Andric   bool calculated_children = false;
3285480093f4SDimitry Andric   std::vector<Row> children;
3286480093f4SDimitry Andric 
RowRow3287480093f4SDimitry Andric   Row(const ValueObjectSP &v, Row *p)
3288*5f7ddb14SDimitry Andric       : value(v), parent(p),
3289af732203SDimitry Andric         might_have_children(v ? v->MightHaveChildren() : false) {}
3290480093f4SDimitry Andric 
GetDepthRow3291480093f4SDimitry Andric   size_t GetDepth() const {
3292480093f4SDimitry Andric     if (parent)
3293480093f4SDimitry Andric       return 1 + parent->GetDepth();
3294480093f4SDimitry Andric     return 0;
3295480093f4SDimitry Andric   }
3296480093f4SDimitry Andric 
ExpandRow3297480093f4SDimitry Andric   void Expand() { expanded = true; }
3298480093f4SDimitry Andric 
GetChildrenRow3299480093f4SDimitry Andric   std::vector<Row> &GetChildren() {
3300480093f4SDimitry Andric     ProcessSP process_sp = value.GetProcessSP();
3301480093f4SDimitry Andric     auto stop_id = process_sp->GetStopID();
3302480093f4SDimitry Andric     if (process_sp && stop_id != children_stop_id) {
3303480093f4SDimitry Andric       children_stop_id = stop_id;
3304480093f4SDimitry Andric       calculated_children = false;
3305480093f4SDimitry Andric     }
3306480093f4SDimitry Andric     if (!calculated_children) {
3307480093f4SDimitry Andric       children.clear();
3308480093f4SDimitry Andric       calculated_children = true;
3309480093f4SDimitry Andric       ValueObjectSP valobj = value.GetSP();
3310480093f4SDimitry Andric       if (valobj) {
3311480093f4SDimitry Andric         const size_t num_children = valobj->GetNumChildren();
3312480093f4SDimitry Andric         for (size_t i = 0; i < num_children; ++i) {
3313480093f4SDimitry Andric           children.push_back(Row(valobj->GetChildAtIndex(i, true), this));
3314480093f4SDimitry Andric         }
3315480093f4SDimitry Andric       }
3316480093f4SDimitry Andric     }
3317480093f4SDimitry Andric     return children;
3318480093f4SDimitry Andric   }
3319480093f4SDimitry Andric 
UnexpandRow3320480093f4SDimitry Andric   void Unexpand() {
3321480093f4SDimitry Andric     expanded = false;
3322480093f4SDimitry Andric     calculated_children = false;
3323480093f4SDimitry Andric     children.clear();
3324480093f4SDimitry Andric   }
3325480093f4SDimitry Andric 
DrawTreeRow3326480093f4SDimitry Andric   void DrawTree(Window &window) {
3327480093f4SDimitry Andric     if (parent)
3328480093f4SDimitry Andric       parent->DrawTreeForChild(window, this, 0);
3329480093f4SDimitry Andric 
3330480093f4SDimitry Andric     if (might_have_children) {
3331480093f4SDimitry Andric       // It we can get UTF8 characters to work we should try to use the
3332480093f4SDimitry Andric       // "symbol" UTF8 string below
3333480093f4SDimitry Andric       //            const char *symbol = "";
3334480093f4SDimitry Andric       //            if (row.expanded)
3335480093f4SDimitry Andric       //                symbol = "\xe2\x96\xbd ";
3336480093f4SDimitry Andric       //            else
3337480093f4SDimitry Andric       //                symbol = "\xe2\x96\xb7 ";
3338480093f4SDimitry Andric       //            window.PutCString (symbol);
3339480093f4SDimitry Andric 
3340480093f4SDimitry Andric       // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v'
3341480093f4SDimitry Andric       // or '>' character...
3342480093f4SDimitry Andric       //            if (expanded)
3343480093f4SDimitry Andric       //                window.PutChar (ACS_DARROW);
3344480093f4SDimitry Andric       //            else
3345480093f4SDimitry Andric       //                window.PutChar (ACS_RARROW);
3346480093f4SDimitry Andric       // Since we can't find any good looking right arrow/down arrow symbols,
3347480093f4SDimitry Andric       // just use a diamond...
3348480093f4SDimitry Andric       window.PutChar(ACS_DIAMOND);
3349480093f4SDimitry Andric       window.PutChar(ACS_HLINE);
3350480093f4SDimitry Andric     }
3351480093f4SDimitry Andric   }
3352480093f4SDimitry Andric 
DrawTreeForChildRow3353480093f4SDimitry Andric   void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) {
3354480093f4SDimitry Andric     if (parent)
3355480093f4SDimitry Andric       parent->DrawTreeForChild(window, this, reverse_depth + 1);
3356480093f4SDimitry Andric 
3357480093f4SDimitry Andric     if (&GetChildren().back() == child) {
3358480093f4SDimitry Andric       // Last child
3359480093f4SDimitry Andric       if (reverse_depth == 0) {
3360480093f4SDimitry Andric         window.PutChar(ACS_LLCORNER);
3361480093f4SDimitry Andric         window.PutChar(ACS_HLINE);
3362480093f4SDimitry Andric       } else {
3363480093f4SDimitry Andric         window.PutChar(' ');
3364480093f4SDimitry Andric         window.PutChar(' ');
3365480093f4SDimitry Andric       }
3366480093f4SDimitry Andric     } else {
3367480093f4SDimitry Andric       if (reverse_depth == 0) {
3368480093f4SDimitry Andric         window.PutChar(ACS_LTEE);
3369480093f4SDimitry Andric         window.PutChar(ACS_HLINE);
3370480093f4SDimitry Andric       } else {
3371480093f4SDimitry Andric         window.PutChar(ACS_VLINE);
3372480093f4SDimitry Andric         window.PutChar(' ');
3373480093f4SDimitry Andric       }
3374480093f4SDimitry Andric     }
3375480093f4SDimitry Andric   }
3376480093f4SDimitry Andric };
3377480093f4SDimitry Andric 
3378480093f4SDimitry Andric struct DisplayOptions {
3379480093f4SDimitry Andric   bool show_types;
3380480093f4SDimitry Andric };
3381480093f4SDimitry Andric 
3382480093f4SDimitry Andric class TreeItem;
3383480093f4SDimitry Andric 
3384480093f4SDimitry Andric class TreeDelegate {
3385480093f4SDimitry Andric public:
3386480093f4SDimitry Andric   TreeDelegate() = default;
3387480093f4SDimitry Andric   virtual ~TreeDelegate() = default;
3388480093f4SDimitry Andric 
3389480093f4SDimitry Andric   virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0;
3390480093f4SDimitry Andric   virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0;
TreeDelegateUpdateSelection(TreeItem & root,int & selection_index,TreeItem * & selected_item)3391*5f7ddb14SDimitry Andric   virtual void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
3392*5f7ddb14SDimitry Andric                                            TreeItem *&selected_item) {
3393*5f7ddb14SDimitry Andric     return;
3394*5f7ddb14SDimitry Andric   }
3395480093f4SDimitry Andric   virtual bool TreeDelegateItemSelected(
3396480093f4SDimitry Andric       TreeItem &item) = 0; // Return true if we need to update views
TreeDelegateExpandRootByDefault()3397*5f7ddb14SDimitry Andric   virtual bool TreeDelegateExpandRootByDefault() { return false; }
3398480093f4SDimitry Andric };
3399480093f4SDimitry Andric 
3400480093f4SDimitry Andric typedef std::shared_ptr<TreeDelegate> TreeDelegateSP;
3401480093f4SDimitry Andric 
3402480093f4SDimitry Andric class TreeItem {
3403480093f4SDimitry Andric public:
TreeItem(TreeItem * parent,TreeDelegate & delegate,bool might_have_children)3404480093f4SDimitry Andric   TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children)
3405480093f4SDimitry Andric       : m_parent(parent), m_delegate(delegate), m_user_data(nullptr),
3406480093f4SDimitry Andric         m_identifier(0), m_row_idx(-1), m_children(),
3407*5f7ddb14SDimitry Andric         m_might_have_children(might_have_children), m_is_expanded(false) {
3408*5f7ddb14SDimitry Andric     if (m_parent == nullptr)
3409*5f7ddb14SDimitry Andric       m_is_expanded = m_delegate.TreeDelegateExpandRootByDefault();
3410*5f7ddb14SDimitry Andric   }
3411480093f4SDimitry Andric 
operator =(const TreeItem & rhs)3412480093f4SDimitry Andric   TreeItem &operator=(const TreeItem &rhs) {
3413480093f4SDimitry Andric     if (this != &rhs) {
3414480093f4SDimitry Andric       m_parent = rhs.m_parent;
3415480093f4SDimitry Andric       m_delegate = rhs.m_delegate;
3416480093f4SDimitry Andric       m_user_data = rhs.m_user_data;
3417480093f4SDimitry Andric       m_identifier = rhs.m_identifier;
3418480093f4SDimitry Andric       m_row_idx = rhs.m_row_idx;
3419480093f4SDimitry Andric       m_children = rhs.m_children;
3420480093f4SDimitry Andric       m_might_have_children = rhs.m_might_have_children;
3421480093f4SDimitry Andric       m_is_expanded = rhs.m_is_expanded;
3422480093f4SDimitry Andric     }
3423480093f4SDimitry Andric     return *this;
3424480093f4SDimitry Andric   }
3425480093f4SDimitry Andric 
3426480093f4SDimitry Andric   TreeItem(const TreeItem &) = default;
3427480093f4SDimitry Andric 
GetDepth() const3428480093f4SDimitry Andric   size_t GetDepth() const {
3429480093f4SDimitry Andric     if (m_parent)
3430480093f4SDimitry Andric       return 1 + m_parent->GetDepth();
3431480093f4SDimitry Andric     return 0;
3432480093f4SDimitry Andric   }
3433480093f4SDimitry Andric 
GetRowIndex() const3434480093f4SDimitry Andric   int GetRowIndex() const { return m_row_idx; }
3435480093f4SDimitry Andric 
ClearChildren()3436480093f4SDimitry Andric   void ClearChildren() { m_children.clear(); }
3437480093f4SDimitry Andric 
Resize(size_t n,const TreeItem & t)3438480093f4SDimitry Andric   void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); }
3439480093f4SDimitry Andric 
operator [](size_t i)3440480093f4SDimitry Andric   TreeItem &operator[](size_t i) { return m_children[i]; }
3441480093f4SDimitry Andric 
SetRowIndex(int row_idx)3442480093f4SDimitry Andric   void SetRowIndex(int row_idx) { m_row_idx = row_idx; }
3443480093f4SDimitry Andric 
GetNumChildren()3444480093f4SDimitry Andric   size_t GetNumChildren() {
3445480093f4SDimitry Andric     m_delegate.TreeDelegateGenerateChildren(*this);
3446480093f4SDimitry Andric     return m_children.size();
3447480093f4SDimitry Andric   }
3448480093f4SDimitry Andric 
ItemWasSelected()3449480093f4SDimitry Andric   void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); }
3450480093f4SDimitry Andric 
CalculateRowIndexes(int & row_idx)3451480093f4SDimitry Andric   void CalculateRowIndexes(int &row_idx) {
3452480093f4SDimitry Andric     SetRowIndex(row_idx);
3453480093f4SDimitry Andric     ++row_idx;
3454480093f4SDimitry Andric 
3455480093f4SDimitry Andric     const bool expanded = IsExpanded();
3456480093f4SDimitry Andric 
3457480093f4SDimitry Andric     // The root item must calculate its children, or we must calculate the
3458480093f4SDimitry Andric     // number of children if the item is expanded
3459480093f4SDimitry Andric     if (m_parent == nullptr || expanded)
3460480093f4SDimitry Andric       GetNumChildren();
3461480093f4SDimitry Andric 
3462480093f4SDimitry Andric     for (auto &item : m_children) {
3463480093f4SDimitry Andric       if (expanded)
3464480093f4SDimitry Andric         item.CalculateRowIndexes(row_idx);
3465480093f4SDimitry Andric       else
3466480093f4SDimitry Andric         item.SetRowIndex(-1);
3467480093f4SDimitry Andric     }
3468480093f4SDimitry Andric   }
3469480093f4SDimitry Andric 
GetParent()3470480093f4SDimitry Andric   TreeItem *GetParent() { return m_parent; }
3471480093f4SDimitry Andric 
IsExpanded() const3472480093f4SDimitry Andric   bool IsExpanded() const { return m_is_expanded; }
3473480093f4SDimitry Andric 
Expand()3474480093f4SDimitry Andric   void Expand() { m_is_expanded = true; }
3475480093f4SDimitry Andric 
Unexpand()3476480093f4SDimitry Andric   void Unexpand() { m_is_expanded = false; }
3477480093f4SDimitry Andric 
Draw(Window & window,const int first_visible_row,const uint32_t selected_row_idx,int & row_idx,int & num_rows_left)3478480093f4SDimitry Andric   bool Draw(Window &window, const int first_visible_row,
3479480093f4SDimitry Andric             const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) {
3480480093f4SDimitry Andric     if (num_rows_left <= 0)
3481480093f4SDimitry Andric       return false;
3482480093f4SDimitry Andric 
3483480093f4SDimitry Andric     if (m_row_idx >= first_visible_row) {
3484480093f4SDimitry Andric       window.MoveCursor(2, row_idx + 1);
3485480093f4SDimitry Andric 
3486480093f4SDimitry Andric       if (m_parent)
3487480093f4SDimitry Andric         m_parent->DrawTreeForChild(window, this, 0);
3488480093f4SDimitry Andric 
3489480093f4SDimitry Andric       if (m_might_have_children) {
3490480093f4SDimitry Andric         // It we can get UTF8 characters to work we should try to use the
3491480093f4SDimitry Andric         // "symbol" UTF8 string below
3492480093f4SDimitry Andric         //            const char *symbol = "";
3493480093f4SDimitry Andric         //            if (row.expanded)
3494480093f4SDimitry Andric         //                symbol = "\xe2\x96\xbd ";
3495480093f4SDimitry Andric         //            else
3496480093f4SDimitry Andric         //                symbol = "\xe2\x96\xb7 ";
3497480093f4SDimitry Andric         //            window.PutCString (symbol);
3498480093f4SDimitry Andric 
3499480093f4SDimitry Andric         // The ACS_DARROW and ACS_RARROW don't look very nice they are just a
3500480093f4SDimitry Andric         // 'v' or '>' character...
3501480093f4SDimitry Andric         //            if (expanded)
3502480093f4SDimitry Andric         //                window.PutChar (ACS_DARROW);
3503480093f4SDimitry Andric         //            else
3504480093f4SDimitry Andric         //                window.PutChar (ACS_RARROW);
3505480093f4SDimitry Andric         // Since we can't find any good looking right arrow/down arrow symbols,
3506480093f4SDimitry Andric         // just use a diamond...
3507480093f4SDimitry Andric         window.PutChar(ACS_DIAMOND);
3508480093f4SDimitry Andric         window.PutChar(ACS_HLINE);
3509480093f4SDimitry Andric       }
3510480093f4SDimitry Andric       bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) &&
3511480093f4SDimitry Andric                        window.IsActive();
3512480093f4SDimitry Andric 
3513480093f4SDimitry Andric       if (highlight)
3514480093f4SDimitry Andric         window.AttributeOn(A_REVERSE);
3515480093f4SDimitry Andric 
3516480093f4SDimitry Andric       m_delegate.TreeDelegateDrawTreeItem(*this, window);
3517480093f4SDimitry Andric 
3518480093f4SDimitry Andric       if (highlight)
3519480093f4SDimitry Andric         window.AttributeOff(A_REVERSE);
3520480093f4SDimitry Andric       ++row_idx;
3521480093f4SDimitry Andric       --num_rows_left;
3522480093f4SDimitry Andric     }
3523480093f4SDimitry Andric 
3524480093f4SDimitry Andric     if (num_rows_left <= 0)
3525480093f4SDimitry Andric       return false; // We are done drawing...
3526480093f4SDimitry Andric 
3527480093f4SDimitry Andric     if (IsExpanded()) {
3528480093f4SDimitry Andric       for (auto &item : m_children) {
3529480093f4SDimitry Andric         // If we displayed all the rows and item.Draw() returns false we are
3530480093f4SDimitry Andric         // done drawing and can exit this for loop
3531480093f4SDimitry Andric         if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx,
3532480093f4SDimitry Andric                        num_rows_left))
3533480093f4SDimitry Andric           break;
3534480093f4SDimitry Andric       }
3535480093f4SDimitry Andric     }
3536480093f4SDimitry Andric     return num_rows_left >= 0; // Return true if not done drawing yet
3537480093f4SDimitry Andric   }
3538480093f4SDimitry Andric 
DrawTreeForChild(Window & window,TreeItem * child,uint32_t reverse_depth)3539480093f4SDimitry Andric   void DrawTreeForChild(Window &window, TreeItem *child,
3540480093f4SDimitry Andric                         uint32_t reverse_depth) {
3541480093f4SDimitry Andric     if (m_parent)
3542480093f4SDimitry Andric       m_parent->DrawTreeForChild(window, this, reverse_depth + 1);
3543480093f4SDimitry Andric 
3544480093f4SDimitry Andric     if (&m_children.back() == child) {
3545480093f4SDimitry Andric       // Last child
3546480093f4SDimitry Andric       if (reverse_depth == 0) {
3547480093f4SDimitry Andric         window.PutChar(ACS_LLCORNER);
3548480093f4SDimitry Andric         window.PutChar(ACS_HLINE);
3549480093f4SDimitry Andric       } else {
3550480093f4SDimitry Andric         window.PutChar(' ');
3551480093f4SDimitry Andric         window.PutChar(' ');
3552480093f4SDimitry Andric       }
3553480093f4SDimitry Andric     } else {
3554480093f4SDimitry Andric       if (reverse_depth == 0) {
3555480093f4SDimitry Andric         window.PutChar(ACS_LTEE);
3556480093f4SDimitry Andric         window.PutChar(ACS_HLINE);
3557480093f4SDimitry Andric       } else {
3558480093f4SDimitry Andric         window.PutChar(ACS_VLINE);
3559480093f4SDimitry Andric         window.PutChar(' ');
3560480093f4SDimitry Andric       }
3561480093f4SDimitry Andric     }
3562480093f4SDimitry Andric   }
3563480093f4SDimitry Andric 
GetItemForRowIndex(uint32_t row_idx)3564480093f4SDimitry Andric   TreeItem *GetItemForRowIndex(uint32_t row_idx) {
3565480093f4SDimitry Andric     if (static_cast<uint32_t>(m_row_idx) == row_idx)
3566480093f4SDimitry Andric       return this;
3567480093f4SDimitry Andric     if (m_children.empty())
3568480093f4SDimitry Andric       return nullptr;
3569480093f4SDimitry Andric     if (IsExpanded()) {
3570480093f4SDimitry Andric       for (auto &item : m_children) {
3571480093f4SDimitry Andric         TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx);
3572480093f4SDimitry Andric         if (selected_item_ptr)
3573480093f4SDimitry Andric           return selected_item_ptr;
3574480093f4SDimitry Andric       }
3575480093f4SDimitry Andric     }
3576480093f4SDimitry Andric     return nullptr;
3577480093f4SDimitry Andric   }
3578480093f4SDimitry Andric 
GetUserData() const3579480093f4SDimitry Andric   void *GetUserData() const { return m_user_data; }
3580480093f4SDimitry Andric 
SetUserData(void * user_data)3581480093f4SDimitry Andric   void SetUserData(void *user_data) { m_user_data = user_data; }
3582480093f4SDimitry Andric 
GetIdentifier() const3583480093f4SDimitry Andric   uint64_t GetIdentifier() const { return m_identifier; }
3584480093f4SDimitry Andric 
SetIdentifier(uint64_t identifier)3585480093f4SDimitry Andric   void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
3586480093f4SDimitry Andric 
SetMightHaveChildren(bool b)3587480093f4SDimitry Andric   void SetMightHaveChildren(bool b) { m_might_have_children = b; }
3588480093f4SDimitry Andric 
3589480093f4SDimitry Andric protected:
3590480093f4SDimitry Andric   TreeItem *m_parent;
3591480093f4SDimitry Andric   TreeDelegate &m_delegate;
3592480093f4SDimitry Andric   void *m_user_data;
3593480093f4SDimitry Andric   uint64_t m_identifier;
3594480093f4SDimitry Andric   int m_row_idx; // Zero based visible row index, -1 if not visible or for the
3595480093f4SDimitry Andric                  // root item
3596480093f4SDimitry Andric   std::vector<TreeItem> m_children;
3597480093f4SDimitry Andric   bool m_might_have_children;
3598480093f4SDimitry Andric   bool m_is_expanded;
3599480093f4SDimitry Andric };
3600480093f4SDimitry Andric 
3601480093f4SDimitry Andric class TreeWindowDelegate : public WindowDelegate {
3602480093f4SDimitry Andric public:
TreeWindowDelegate(Debugger & debugger,const TreeDelegateSP & delegate_sp)3603480093f4SDimitry Andric   TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp)
3604480093f4SDimitry Andric       : m_debugger(debugger), m_delegate_sp(delegate_sp),
3605480093f4SDimitry Andric         m_root(nullptr, *delegate_sp, true), m_selected_item(nullptr),
3606480093f4SDimitry Andric         m_num_rows(0), m_selected_row_idx(0), m_first_visible_row(0),
3607480093f4SDimitry Andric         m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {}
3608480093f4SDimitry Andric 
NumVisibleRows() const3609480093f4SDimitry Andric   int NumVisibleRows() const { return m_max_y - m_min_y; }
3610480093f4SDimitry Andric 
WindowDelegateDraw(Window & window,bool force)3611480093f4SDimitry Andric   bool WindowDelegateDraw(Window &window, bool force) override {
3612480093f4SDimitry Andric     ExecutionContext exe_ctx(
3613480093f4SDimitry Andric         m_debugger.GetCommandInterpreter().GetExecutionContext());
3614480093f4SDimitry Andric     Process *process = exe_ctx.GetProcessPtr();
3615480093f4SDimitry Andric 
3616480093f4SDimitry Andric     bool display_content = false;
3617480093f4SDimitry Andric     if (process) {
3618480093f4SDimitry Andric       StateType state = process->GetState();
3619480093f4SDimitry Andric       if (StateIsStoppedState(state, true)) {
3620480093f4SDimitry Andric         // We are stopped, so it is ok to
3621480093f4SDimitry Andric         display_content = true;
3622480093f4SDimitry Andric       } else if (StateIsRunningState(state)) {
3623480093f4SDimitry Andric         return true; // Don't do any updating when we are running
3624480093f4SDimitry Andric       }
3625480093f4SDimitry Andric     }
3626480093f4SDimitry Andric 
3627480093f4SDimitry Andric     m_min_x = 2;
3628480093f4SDimitry Andric     m_min_y = 1;
3629480093f4SDimitry Andric     m_max_x = window.GetWidth() - 1;
3630480093f4SDimitry Andric     m_max_y = window.GetHeight() - 1;
3631480093f4SDimitry Andric 
3632480093f4SDimitry Andric     window.Erase();
3633480093f4SDimitry Andric     window.DrawTitleBox(window.GetName());
3634480093f4SDimitry Andric 
3635480093f4SDimitry Andric     if (display_content) {
3636480093f4SDimitry Andric       const int num_visible_rows = NumVisibleRows();
3637480093f4SDimitry Andric       m_num_rows = 0;
3638480093f4SDimitry Andric       m_root.CalculateRowIndexes(m_num_rows);
3639*5f7ddb14SDimitry Andric       m_delegate_sp->TreeDelegateUpdateSelection(m_root, m_selected_row_idx,
3640*5f7ddb14SDimitry Andric                                                  m_selected_item);
3641480093f4SDimitry Andric 
3642480093f4SDimitry Andric       // If we unexpanded while having something selected our total number of
3643480093f4SDimitry Andric       // rows is less than the num visible rows, then make sure we show all the
3644480093f4SDimitry Andric       // rows by setting the first visible row accordingly.
3645480093f4SDimitry Andric       if (m_first_visible_row > 0 && m_num_rows < num_visible_rows)
3646480093f4SDimitry Andric         m_first_visible_row = 0;
3647480093f4SDimitry Andric 
3648480093f4SDimitry Andric       // Make sure the selected row is always visible
3649480093f4SDimitry Andric       if (m_selected_row_idx < m_first_visible_row)
3650480093f4SDimitry Andric         m_first_visible_row = m_selected_row_idx;
3651480093f4SDimitry Andric       else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
3652480093f4SDimitry Andric         m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
3653480093f4SDimitry Andric 
3654480093f4SDimitry Andric       int row_idx = 0;
3655480093f4SDimitry Andric       int num_rows_left = num_visible_rows;
3656480093f4SDimitry Andric       m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx,
3657480093f4SDimitry Andric                   num_rows_left);
3658480093f4SDimitry Andric       // Get the selected row
3659480093f4SDimitry Andric       m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3660480093f4SDimitry Andric     } else {
3661480093f4SDimitry Andric       m_selected_item = nullptr;
3662480093f4SDimitry Andric     }
3663480093f4SDimitry Andric 
3664480093f4SDimitry Andric     return true; // Drawing handled
3665480093f4SDimitry Andric   }
3666480093f4SDimitry Andric 
WindowDelegateGetHelpText()3667480093f4SDimitry Andric   const char *WindowDelegateGetHelpText() override {
3668480093f4SDimitry Andric     return "Thread window keyboard shortcuts:";
3669480093f4SDimitry Andric   }
3670480093f4SDimitry Andric 
WindowDelegateGetKeyHelp()3671480093f4SDimitry Andric   KeyHelp *WindowDelegateGetKeyHelp() override {
3672480093f4SDimitry Andric     static curses::KeyHelp g_source_view_key_help[] = {
3673480093f4SDimitry Andric         {KEY_UP, "Select previous item"},
3674480093f4SDimitry Andric         {KEY_DOWN, "Select next item"},
3675480093f4SDimitry Andric         {KEY_RIGHT, "Expand the selected item"},
3676480093f4SDimitry Andric         {KEY_LEFT,
3677480093f4SDimitry Andric          "Unexpand the selected item or select parent if not expanded"},
3678480093f4SDimitry Andric         {KEY_PPAGE, "Page up"},
3679480093f4SDimitry Andric         {KEY_NPAGE, "Page down"},
3680480093f4SDimitry Andric         {'h', "Show help dialog"},
3681480093f4SDimitry Andric         {' ', "Toggle item expansion"},
3682480093f4SDimitry Andric         {',', "Page up"},
3683480093f4SDimitry Andric         {'.', "Page down"},
3684480093f4SDimitry Andric         {'\0', nullptr}};
3685480093f4SDimitry Andric     return g_source_view_key_help;
3686480093f4SDimitry Andric   }
3687480093f4SDimitry Andric 
WindowDelegateHandleChar(Window & window,int c)3688480093f4SDimitry Andric   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
3689480093f4SDimitry Andric     switch (c) {
3690480093f4SDimitry Andric     case ',':
3691480093f4SDimitry Andric     case KEY_PPAGE:
3692480093f4SDimitry Andric       // Page up key
3693480093f4SDimitry Andric       if (m_first_visible_row > 0) {
3694480093f4SDimitry Andric         if (m_first_visible_row > m_max_y)
3695480093f4SDimitry Andric           m_first_visible_row -= m_max_y;
3696480093f4SDimitry Andric         else
3697480093f4SDimitry Andric           m_first_visible_row = 0;
3698480093f4SDimitry Andric         m_selected_row_idx = m_first_visible_row;
3699480093f4SDimitry Andric         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3700480093f4SDimitry Andric         if (m_selected_item)
3701480093f4SDimitry Andric           m_selected_item->ItemWasSelected();
3702480093f4SDimitry Andric       }
3703480093f4SDimitry Andric       return eKeyHandled;
3704480093f4SDimitry Andric 
3705480093f4SDimitry Andric     case '.':
3706480093f4SDimitry Andric     case KEY_NPAGE:
3707480093f4SDimitry Andric       // Page down key
3708480093f4SDimitry Andric       if (m_num_rows > m_max_y) {
3709480093f4SDimitry Andric         if (m_first_visible_row + m_max_y < m_num_rows) {
3710480093f4SDimitry Andric           m_first_visible_row += m_max_y;
3711480093f4SDimitry Andric           m_selected_row_idx = m_first_visible_row;
3712480093f4SDimitry Andric           m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3713480093f4SDimitry Andric           if (m_selected_item)
3714480093f4SDimitry Andric             m_selected_item->ItemWasSelected();
3715480093f4SDimitry Andric         }
3716480093f4SDimitry Andric       }
3717480093f4SDimitry Andric       return eKeyHandled;
3718480093f4SDimitry Andric 
3719480093f4SDimitry Andric     case KEY_UP:
3720480093f4SDimitry Andric       if (m_selected_row_idx > 0) {
3721480093f4SDimitry Andric         --m_selected_row_idx;
3722480093f4SDimitry Andric         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3723480093f4SDimitry Andric         if (m_selected_item)
3724480093f4SDimitry Andric           m_selected_item->ItemWasSelected();
3725480093f4SDimitry Andric       }
3726480093f4SDimitry Andric       return eKeyHandled;
3727480093f4SDimitry Andric 
3728480093f4SDimitry Andric     case KEY_DOWN:
3729480093f4SDimitry Andric       if (m_selected_row_idx + 1 < m_num_rows) {
3730480093f4SDimitry Andric         ++m_selected_row_idx;
3731480093f4SDimitry Andric         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3732480093f4SDimitry Andric         if (m_selected_item)
3733480093f4SDimitry Andric           m_selected_item->ItemWasSelected();
3734480093f4SDimitry Andric       }
3735480093f4SDimitry Andric       return eKeyHandled;
3736480093f4SDimitry Andric 
3737480093f4SDimitry Andric     case KEY_RIGHT:
3738480093f4SDimitry Andric       if (m_selected_item) {
3739480093f4SDimitry Andric         if (!m_selected_item->IsExpanded())
3740480093f4SDimitry Andric           m_selected_item->Expand();
3741480093f4SDimitry Andric       }
3742480093f4SDimitry Andric       return eKeyHandled;
3743480093f4SDimitry Andric 
3744480093f4SDimitry Andric     case KEY_LEFT:
3745480093f4SDimitry Andric       if (m_selected_item) {
3746480093f4SDimitry Andric         if (m_selected_item->IsExpanded())
3747480093f4SDimitry Andric           m_selected_item->Unexpand();
3748480093f4SDimitry Andric         else if (m_selected_item->GetParent()) {
3749480093f4SDimitry Andric           m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex();
3750480093f4SDimitry Andric           m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3751480093f4SDimitry Andric           if (m_selected_item)
3752480093f4SDimitry Andric             m_selected_item->ItemWasSelected();
3753480093f4SDimitry Andric         }
3754480093f4SDimitry Andric       }
3755480093f4SDimitry Andric       return eKeyHandled;
3756480093f4SDimitry Andric 
3757480093f4SDimitry Andric     case ' ':
3758480093f4SDimitry Andric       // Toggle expansion state when SPACE is pressed
3759480093f4SDimitry Andric       if (m_selected_item) {
3760480093f4SDimitry Andric         if (m_selected_item->IsExpanded())
3761480093f4SDimitry Andric           m_selected_item->Unexpand();
3762480093f4SDimitry Andric         else
3763480093f4SDimitry Andric           m_selected_item->Expand();
3764480093f4SDimitry Andric       }
3765480093f4SDimitry Andric       return eKeyHandled;
3766480093f4SDimitry Andric 
3767480093f4SDimitry Andric     case 'h':
3768480093f4SDimitry Andric       window.CreateHelpSubwindow();
3769480093f4SDimitry Andric       return eKeyHandled;
3770480093f4SDimitry Andric 
3771480093f4SDimitry Andric     default:
3772480093f4SDimitry Andric       break;
3773480093f4SDimitry Andric     }
3774480093f4SDimitry Andric     return eKeyNotHandled;
3775480093f4SDimitry Andric   }
3776480093f4SDimitry Andric 
3777480093f4SDimitry Andric protected:
3778480093f4SDimitry Andric   Debugger &m_debugger;
3779480093f4SDimitry Andric   TreeDelegateSP m_delegate_sp;
3780480093f4SDimitry Andric   TreeItem m_root;
3781480093f4SDimitry Andric   TreeItem *m_selected_item;
3782480093f4SDimitry Andric   int m_num_rows;
3783480093f4SDimitry Andric   int m_selected_row_idx;
3784480093f4SDimitry Andric   int m_first_visible_row;
3785480093f4SDimitry Andric   int m_min_x;
3786480093f4SDimitry Andric   int m_min_y;
3787480093f4SDimitry Andric   int m_max_x;
3788480093f4SDimitry Andric   int m_max_y;
3789480093f4SDimitry Andric };
3790480093f4SDimitry Andric 
3791480093f4SDimitry Andric class FrameTreeDelegate : public TreeDelegate {
3792480093f4SDimitry Andric public:
FrameTreeDelegate()3793480093f4SDimitry Andric   FrameTreeDelegate() : TreeDelegate() {
3794480093f4SDimitry Andric     FormatEntity::Parse(
3795480093f4SDimitry Andric         "frame #${frame.index}: {${function.name}${function.pc-offset}}}",
3796480093f4SDimitry Andric         m_format);
3797480093f4SDimitry Andric   }
3798480093f4SDimitry Andric 
3799480093f4SDimitry Andric   ~FrameTreeDelegate() override = default;
3800480093f4SDimitry Andric 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)3801480093f4SDimitry Andric   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
3802480093f4SDimitry Andric     Thread *thread = (Thread *)item.GetUserData();
3803480093f4SDimitry Andric     if (thread) {
3804480093f4SDimitry Andric       const uint64_t frame_idx = item.GetIdentifier();
3805480093f4SDimitry Andric       StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx);
3806480093f4SDimitry Andric       if (frame_sp) {
3807480093f4SDimitry Andric         StreamString strm;
3808480093f4SDimitry Andric         const SymbolContext &sc =
3809480093f4SDimitry Andric             frame_sp->GetSymbolContext(eSymbolContextEverything);
3810480093f4SDimitry Andric         ExecutionContext exe_ctx(frame_sp);
3811480093f4SDimitry Andric         if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr,
3812480093f4SDimitry Andric                                  nullptr, false, false)) {
3813480093f4SDimitry Andric           int right_pad = 1;
3814af732203SDimitry Andric           window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
3815480093f4SDimitry Andric         }
3816480093f4SDimitry Andric       }
3817480093f4SDimitry Andric     }
3818480093f4SDimitry Andric   }
3819480093f4SDimitry Andric 
TreeDelegateGenerateChildren(TreeItem & item)3820480093f4SDimitry Andric   void TreeDelegateGenerateChildren(TreeItem &item) override {
3821480093f4SDimitry Andric     // No children for frames yet...
3822480093f4SDimitry Andric   }
3823480093f4SDimitry Andric 
TreeDelegateItemSelected(TreeItem & item)3824480093f4SDimitry Andric   bool TreeDelegateItemSelected(TreeItem &item) override {
3825480093f4SDimitry Andric     Thread *thread = (Thread *)item.GetUserData();
3826480093f4SDimitry Andric     if (thread) {
3827480093f4SDimitry Andric       thread->GetProcess()->GetThreadList().SetSelectedThreadByID(
3828480093f4SDimitry Andric           thread->GetID());
3829480093f4SDimitry Andric       const uint64_t frame_idx = item.GetIdentifier();
3830480093f4SDimitry Andric       thread->SetSelectedFrameByIndex(frame_idx);
3831480093f4SDimitry Andric       return true;
3832480093f4SDimitry Andric     }
3833480093f4SDimitry Andric     return false;
3834480093f4SDimitry Andric   }
3835480093f4SDimitry Andric 
3836480093f4SDimitry Andric protected:
3837480093f4SDimitry Andric   FormatEntity::Entry m_format;
3838480093f4SDimitry Andric };
3839480093f4SDimitry Andric 
3840480093f4SDimitry Andric class ThreadTreeDelegate : public TreeDelegate {
3841480093f4SDimitry Andric public:
ThreadTreeDelegate(Debugger & debugger)3842480093f4SDimitry Andric   ThreadTreeDelegate(Debugger &debugger)
3843480093f4SDimitry Andric       : TreeDelegate(), m_debugger(debugger), m_tid(LLDB_INVALID_THREAD_ID),
3844480093f4SDimitry Andric         m_stop_id(UINT32_MAX) {
3845480093f4SDimitry Andric     FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop "
3846480093f4SDimitry Andric                         "reason = ${thread.stop-reason}}",
3847480093f4SDimitry Andric                         m_format);
3848480093f4SDimitry Andric   }
3849480093f4SDimitry Andric 
3850480093f4SDimitry Andric   ~ThreadTreeDelegate() override = default;
3851480093f4SDimitry Andric 
GetProcess()3852480093f4SDimitry Andric   ProcessSP GetProcess() {
3853480093f4SDimitry Andric     return m_debugger.GetCommandInterpreter()
3854480093f4SDimitry Andric         .GetExecutionContext()
3855480093f4SDimitry Andric         .GetProcessSP();
3856480093f4SDimitry Andric   }
3857480093f4SDimitry Andric 
GetThread(const TreeItem & item)3858480093f4SDimitry Andric   ThreadSP GetThread(const TreeItem &item) {
3859480093f4SDimitry Andric     ProcessSP process_sp = GetProcess();
3860480093f4SDimitry Andric     if (process_sp)
3861480093f4SDimitry Andric       return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier());
3862480093f4SDimitry Andric     return ThreadSP();
3863480093f4SDimitry Andric   }
3864480093f4SDimitry Andric 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)3865480093f4SDimitry Andric   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
3866480093f4SDimitry Andric     ThreadSP thread_sp = GetThread(item);
3867480093f4SDimitry Andric     if (thread_sp) {
3868480093f4SDimitry Andric       StreamString strm;
3869480093f4SDimitry Andric       ExecutionContext exe_ctx(thread_sp);
3870480093f4SDimitry Andric       if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
3871480093f4SDimitry Andric                                nullptr, false, false)) {
3872480093f4SDimitry Andric         int right_pad = 1;
3873af732203SDimitry Andric         window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
3874480093f4SDimitry Andric       }
3875480093f4SDimitry Andric     }
3876480093f4SDimitry Andric   }
3877480093f4SDimitry Andric 
TreeDelegateGenerateChildren(TreeItem & item)3878480093f4SDimitry Andric   void TreeDelegateGenerateChildren(TreeItem &item) override {
3879480093f4SDimitry Andric     ProcessSP process_sp = GetProcess();
3880480093f4SDimitry Andric     if (process_sp && process_sp->IsAlive()) {
3881480093f4SDimitry Andric       StateType state = process_sp->GetState();
3882480093f4SDimitry Andric       if (StateIsStoppedState(state, true)) {
3883480093f4SDimitry Andric         ThreadSP thread_sp = GetThread(item);
3884480093f4SDimitry Andric         if (thread_sp) {
3885480093f4SDimitry Andric           if (m_stop_id == process_sp->GetStopID() &&
3886480093f4SDimitry Andric               thread_sp->GetID() == m_tid)
3887480093f4SDimitry Andric             return; // Children are already up to date
3888480093f4SDimitry Andric           if (!m_frame_delegate_sp) {
3889480093f4SDimitry Andric             // Always expand the thread item the first time we show it
3890480093f4SDimitry Andric             m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>();
3891480093f4SDimitry Andric           }
3892480093f4SDimitry Andric 
3893480093f4SDimitry Andric           m_stop_id = process_sp->GetStopID();
3894480093f4SDimitry Andric           m_tid = thread_sp->GetID();
3895480093f4SDimitry Andric 
3896480093f4SDimitry Andric           TreeItem t(&item, *m_frame_delegate_sp, false);
3897480093f4SDimitry Andric           size_t num_frames = thread_sp->GetStackFrameCount();
3898480093f4SDimitry Andric           item.Resize(num_frames, t);
3899480093f4SDimitry Andric           for (size_t i = 0; i < num_frames; ++i) {
3900480093f4SDimitry Andric             item[i].SetUserData(thread_sp.get());
3901480093f4SDimitry Andric             item[i].SetIdentifier(i);
3902480093f4SDimitry Andric           }
3903480093f4SDimitry Andric         }
3904480093f4SDimitry Andric         return;
3905480093f4SDimitry Andric       }
3906480093f4SDimitry Andric     }
3907480093f4SDimitry Andric     item.ClearChildren();
3908480093f4SDimitry Andric   }
3909480093f4SDimitry Andric 
TreeDelegateItemSelected(TreeItem & item)3910480093f4SDimitry Andric   bool TreeDelegateItemSelected(TreeItem &item) override {
3911480093f4SDimitry Andric     ProcessSP process_sp = GetProcess();
3912480093f4SDimitry Andric     if (process_sp && process_sp->IsAlive()) {
3913480093f4SDimitry Andric       StateType state = process_sp->GetState();
3914480093f4SDimitry Andric       if (StateIsStoppedState(state, true)) {
3915480093f4SDimitry Andric         ThreadSP thread_sp = GetThread(item);
3916480093f4SDimitry Andric         if (thread_sp) {
3917480093f4SDimitry Andric           ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList();
3918480093f4SDimitry Andric           std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex());
3919480093f4SDimitry Andric           ThreadSP selected_thread_sp = thread_list.GetSelectedThread();
3920480093f4SDimitry Andric           if (selected_thread_sp->GetID() != thread_sp->GetID()) {
3921480093f4SDimitry Andric             thread_list.SetSelectedThreadByID(thread_sp->GetID());
3922480093f4SDimitry Andric             return true;
3923480093f4SDimitry Andric           }
3924480093f4SDimitry Andric         }
3925480093f4SDimitry Andric       }
3926480093f4SDimitry Andric     }
3927480093f4SDimitry Andric     return false;
3928480093f4SDimitry Andric   }
3929480093f4SDimitry Andric 
3930480093f4SDimitry Andric protected:
3931480093f4SDimitry Andric   Debugger &m_debugger;
3932480093f4SDimitry Andric   std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp;
3933480093f4SDimitry Andric   lldb::user_id_t m_tid;
3934480093f4SDimitry Andric   uint32_t m_stop_id;
3935480093f4SDimitry Andric   FormatEntity::Entry m_format;
3936480093f4SDimitry Andric };
3937480093f4SDimitry Andric 
3938480093f4SDimitry Andric class ThreadsTreeDelegate : public TreeDelegate {
3939480093f4SDimitry Andric public:
ThreadsTreeDelegate(Debugger & debugger)3940480093f4SDimitry Andric   ThreadsTreeDelegate(Debugger &debugger)
3941480093f4SDimitry Andric       : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger),
3942*5f7ddb14SDimitry Andric         m_stop_id(UINT32_MAX), m_update_selection(false) {
3943480093f4SDimitry Andric     FormatEntity::Parse("process ${process.id}{, name = ${process.name}}",
3944480093f4SDimitry Andric                         m_format);
3945480093f4SDimitry Andric   }
3946480093f4SDimitry Andric 
3947480093f4SDimitry Andric   ~ThreadsTreeDelegate() override = default;
3948480093f4SDimitry Andric 
GetProcess()3949480093f4SDimitry Andric   ProcessSP GetProcess() {
3950480093f4SDimitry Andric     return m_debugger.GetCommandInterpreter()
3951480093f4SDimitry Andric         .GetExecutionContext()
3952480093f4SDimitry Andric         .GetProcessSP();
3953480093f4SDimitry Andric   }
3954480093f4SDimitry Andric 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)3955480093f4SDimitry Andric   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
3956480093f4SDimitry Andric     ProcessSP process_sp = GetProcess();
3957480093f4SDimitry Andric     if (process_sp && process_sp->IsAlive()) {
3958480093f4SDimitry Andric       StreamString strm;
3959480093f4SDimitry Andric       ExecutionContext exe_ctx(process_sp);
3960480093f4SDimitry Andric       if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
3961480093f4SDimitry Andric                                nullptr, false, false)) {
3962480093f4SDimitry Andric         int right_pad = 1;
3963af732203SDimitry Andric         window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
3964480093f4SDimitry Andric       }
3965480093f4SDimitry Andric     }
3966480093f4SDimitry Andric   }
3967480093f4SDimitry Andric 
TreeDelegateGenerateChildren(TreeItem & item)3968480093f4SDimitry Andric   void TreeDelegateGenerateChildren(TreeItem &item) override {
3969480093f4SDimitry Andric     ProcessSP process_sp = GetProcess();
3970*5f7ddb14SDimitry Andric     m_update_selection = false;
3971480093f4SDimitry Andric     if (process_sp && process_sp->IsAlive()) {
3972480093f4SDimitry Andric       StateType state = process_sp->GetState();
3973480093f4SDimitry Andric       if (StateIsStoppedState(state, true)) {
3974480093f4SDimitry Andric         const uint32_t stop_id = process_sp->GetStopID();
3975480093f4SDimitry Andric         if (m_stop_id == stop_id)
3976480093f4SDimitry Andric           return; // Children are already up to date
3977480093f4SDimitry Andric 
3978480093f4SDimitry Andric         m_stop_id = stop_id;
3979*5f7ddb14SDimitry Andric         m_update_selection = true;
3980480093f4SDimitry Andric 
3981480093f4SDimitry Andric         if (!m_thread_delegate_sp) {
3982480093f4SDimitry Andric           // Always expand the thread item the first time we show it
3983480093f4SDimitry Andric           // item.Expand();
3984480093f4SDimitry Andric           m_thread_delegate_sp =
3985480093f4SDimitry Andric               std::make_shared<ThreadTreeDelegate>(m_debugger);
3986480093f4SDimitry Andric         }
3987480093f4SDimitry Andric 
3988480093f4SDimitry Andric         TreeItem t(&item, *m_thread_delegate_sp, false);
3989480093f4SDimitry Andric         ThreadList &threads = process_sp->GetThreadList();
3990480093f4SDimitry Andric         std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
3991*5f7ddb14SDimitry Andric         ThreadSP selected_thread = threads.GetSelectedThread();
3992480093f4SDimitry Andric         size_t num_threads = threads.GetSize();
3993480093f4SDimitry Andric         item.Resize(num_threads, t);
3994480093f4SDimitry Andric         for (size_t i = 0; i < num_threads; ++i) {
3995*5f7ddb14SDimitry Andric           ThreadSP thread = threads.GetThreadAtIndex(i);
3996*5f7ddb14SDimitry Andric           item[i].SetIdentifier(thread->GetID());
3997480093f4SDimitry Andric           item[i].SetMightHaveChildren(true);
3998*5f7ddb14SDimitry Andric           if (selected_thread->GetID() == thread->GetID())
3999*5f7ddb14SDimitry Andric             item[i].Expand();
4000480093f4SDimitry Andric         }
4001480093f4SDimitry Andric         return;
4002480093f4SDimitry Andric       }
4003480093f4SDimitry Andric     }
4004480093f4SDimitry Andric     item.ClearChildren();
4005480093f4SDimitry Andric   }
4006480093f4SDimitry Andric 
TreeDelegateUpdateSelection(TreeItem & root,int & selection_index,TreeItem * & selected_item)4007*5f7ddb14SDimitry Andric   void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
4008*5f7ddb14SDimitry Andric                                    TreeItem *&selected_item) override {
4009*5f7ddb14SDimitry Andric     if (!m_update_selection)
4010*5f7ddb14SDimitry Andric       return;
4011*5f7ddb14SDimitry Andric 
4012*5f7ddb14SDimitry Andric     ProcessSP process_sp = GetProcess();
4013*5f7ddb14SDimitry Andric     if (!(process_sp && process_sp->IsAlive()))
4014*5f7ddb14SDimitry Andric       return;
4015*5f7ddb14SDimitry Andric 
4016*5f7ddb14SDimitry Andric     StateType state = process_sp->GetState();
4017*5f7ddb14SDimitry Andric     if (!StateIsStoppedState(state, true))
4018*5f7ddb14SDimitry Andric       return;
4019*5f7ddb14SDimitry Andric 
4020*5f7ddb14SDimitry Andric     ThreadList &threads = process_sp->GetThreadList();
4021*5f7ddb14SDimitry Andric     std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
4022*5f7ddb14SDimitry Andric     ThreadSP selected_thread = threads.GetSelectedThread();
4023*5f7ddb14SDimitry Andric     size_t num_threads = threads.GetSize();
4024*5f7ddb14SDimitry Andric     for (size_t i = 0; i < num_threads; ++i) {
4025*5f7ddb14SDimitry Andric       ThreadSP thread = threads.GetThreadAtIndex(i);
4026*5f7ddb14SDimitry Andric       if (selected_thread->GetID() == thread->GetID()) {
4027*5f7ddb14SDimitry Andric         selected_item = &root[i][thread->GetSelectedFrameIndex()];
4028*5f7ddb14SDimitry Andric         selection_index = selected_item->GetRowIndex();
4029*5f7ddb14SDimitry Andric         return;
4030*5f7ddb14SDimitry Andric       }
4031*5f7ddb14SDimitry Andric     }
4032*5f7ddb14SDimitry Andric   }
4033*5f7ddb14SDimitry Andric 
TreeDelegateItemSelected(TreeItem & item)4034480093f4SDimitry Andric   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
4035480093f4SDimitry Andric 
TreeDelegateExpandRootByDefault()4036*5f7ddb14SDimitry Andric   bool TreeDelegateExpandRootByDefault() override { return true; }
4037*5f7ddb14SDimitry Andric 
4038480093f4SDimitry Andric protected:
4039480093f4SDimitry Andric   std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp;
4040480093f4SDimitry Andric   Debugger &m_debugger;
4041480093f4SDimitry Andric   uint32_t m_stop_id;
4042*5f7ddb14SDimitry Andric   bool m_update_selection;
4043480093f4SDimitry Andric   FormatEntity::Entry m_format;
4044480093f4SDimitry Andric };
4045480093f4SDimitry Andric 
4046480093f4SDimitry Andric class ValueObjectListDelegate : public WindowDelegate {
4047480093f4SDimitry Andric public:
ValueObjectListDelegate()4048*5f7ddb14SDimitry Andric   ValueObjectListDelegate() : m_rows() {}
4049480093f4SDimitry Andric 
ValueObjectListDelegate(ValueObjectList & valobj_list)4050480093f4SDimitry Andric   ValueObjectListDelegate(ValueObjectList &valobj_list)
4051480093f4SDimitry Andric       : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0),
4052480093f4SDimitry Andric         m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) {
4053480093f4SDimitry Andric     SetValues(valobj_list);
4054480093f4SDimitry Andric   }
4055480093f4SDimitry Andric 
4056480093f4SDimitry Andric   ~ValueObjectListDelegate() override = default;
4057480093f4SDimitry Andric 
SetValues(ValueObjectList & valobj_list)4058480093f4SDimitry Andric   void SetValues(ValueObjectList &valobj_list) {
4059480093f4SDimitry Andric     m_selected_row = nullptr;
4060480093f4SDimitry Andric     m_selected_row_idx = 0;
4061480093f4SDimitry Andric     m_first_visible_row = 0;
4062480093f4SDimitry Andric     m_num_rows = 0;
4063480093f4SDimitry Andric     m_rows.clear();
4064480093f4SDimitry Andric     for (auto &valobj_sp : valobj_list.GetObjects())
4065480093f4SDimitry Andric       m_rows.push_back(Row(valobj_sp, nullptr));
4066480093f4SDimitry Andric   }
4067480093f4SDimitry Andric 
WindowDelegateDraw(Window & window,bool force)4068480093f4SDimitry Andric   bool WindowDelegateDraw(Window &window, bool force) override {
4069480093f4SDimitry Andric     m_num_rows = 0;
4070480093f4SDimitry Andric     m_min_x = 2;
4071480093f4SDimitry Andric     m_min_y = 1;
4072480093f4SDimitry Andric     m_max_x = window.GetWidth() - 1;
4073480093f4SDimitry Andric     m_max_y = window.GetHeight() - 1;
4074480093f4SDimitry Andric 
4075480093f4SDimitry Andric     window.Erase();
4076480093f4SDimitry Andric     window.DrawTitleBox(window.GetName());
4077480093f4SDimitry Andric 
4078480093f4SDimitry Andric     const int num_visible_rows = NumVisibleRows();
4079480093f4SDimitry Andric     const int num_rows = CalculateTotalNumberRows(m_rows);
4080480093f4SDimitry Andric 
4081480093f4SDimitry Andric     // If we unexpanded while having something selected our total number of
4082480093f4SDimitry Andric     // rows is less than the num visible rows, then make sure we show all the
4083480093f4SDimitry Andric     // rows by setting the first visible row accordingly.
4084480093f4SDimitry Andric     if (m_first_visible_row > 0 && num_rows < num_visible_rows)
4085480093f4SDimitry Andric       m_first_visible_row = 0;
4086480093f4SDimitry Andric 
4087480093f4SDimitry Andric     // Make sure the selected row is always visible
4088480093f4SDimitry Andric     if (m_selected_row_idx < m_first_visible_row)
4089480093f4SDimitry Andric       m_first_visible_row = m_selected_row_idx;
4090480093f4SDimitry Andric     else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
4091480093f4SDimitry Andric       m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
4092480093f4SDimitry Andric 
4093480093f4SDimitry Andric     DisplayRows(window, m_rows, g_options);
4094480093f4SDimitry Andric 
4095480093f4SDimitry Andric     // Get the selected row
4096480093f4SDimitry Andric     m_selected_row = GetRowForRowIndex(m_selected_row_idx);
4097480093f4SDimitry Andric     // Keep the cursor on the selected row so the highlight and the cursor are
4098480093f4SDimitry Andric     // always on the same line
4099480093f4SDimitry Andric     if (m_selected_row)
4100480093f4SDimitry Andric       window.MoveCursor(m_selected_row->x, m_selected_row->y);
4101480093f4SDimitry Andric 
4102480093f4SDimitry Andric     return true; // Drawing handled
4103480093f4SDimitry Andric   }
4104480093f4SDimitry Andric 
WindowDelegateGetKeyHelp()4105480093f4SDimitry Andric   KeyHelp *WindowDelegateGetKeyHelp() override {
4106480093f4SDimitry Andric     static curses::KeyHelp g_source_view_key_help[] = {
4107480093f4SDimitry Andric         {KEY_UP, "Select previous item"},
4108480093f4SDimitry Andric         {KEY_DOWN, "Select next item"},
4109480093f4SDimitry Andric         {KEY_RIGHT, "Expand selected item"},
4110480093f4SDimitry Andric         {KEY_LEFT, "Unexpand selected item or select parent if not expanded"},
4111480093f4SDimitry Andric         {KEY_PPAGE, "Page up"},
4112480093f4SDimitry Andric         {KEY_NPAGE, "Page down"},
4113480093f4SDimitry Andric         {'A', "Format as annotated address"},
4114480093f4SDimitry Andric         {'b', "Format as binary"},
4115480093f4SDimitry Andric         {'B', "Format as hex bytes with ASCII"},
4116480093f4SDimitry Andric         {'c', "Format as character"},
4117480093f4SDimitry Andric         {'d', "Format as a signed integer"},
4118480093f4SDimitry Andric         {'D', "Format selected value using the default format for the type"},
4119480093f4SDimitry Andric         {'f', "Format as float"},
4120480093f4SDimitry Andric         {'h', "Show help dialog"},
4121480093f4SDimitry Andric         {'i', "Format as instructions"},
4122480093f4SDimitry Andric         {'o', "Format as octal"},
4123480093f4SDimitry Andric         {'p', "Format as pointer"},
4124480093f4SDimitry Andric         {'s', "Format as C string"},
4125480093f4SDimitry Andric         {'t', "Toggle showing/hiding type names"},
4126480093f4SDimitry Andric         {'u', "Format as an unsigned integer"},
4127480093f4SDimitry Andric         {'x', "Format as hex"},
4128480093f4SDimitry Andric         {'X', "Format as uppercase hex"},
4129480093f4SDimitry Andric         {' ', "Toggle item expansion"},
4130480093f4SDimitry Andric         {',', "Page up"},
4131480093f4SDimitry Andric         {'.', "Page down"},
4132480093f4SDimitry Andric         {'\0', nullptr}};
4133480093f4SDimitry Andric     return g_source_view_key_help;
4134480093f4SDimitry Andric   }
4135480093f4SDimitry Andric 
WindowDelegateHandleChar(Window & window,int c)4136480093f4SDimitry Andric   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
4137480093f4SDimitry Andric     switch (c) {
4138480093f4SDimitry Andric     case 'x':
4139480093f4SDimitry Andric     case 'X':
4140480093f4SDimitry Andric     case 'o':
4141480093f4SDimitry Andric     case 's':
4142480093f4SDimitry Andric     case 'u':
4143480093f4SDimitry Andric     case 'd':
4144480093f4SDimitry Andric     case 'D':
4145480093f4SDimitry Andric     case 'i':
4146480093f4SDimitry Andric     case 'A':
4147480093f4SDimitry Andric     case 'p':
4148480093f4SDimitry Andric     case 'c':
4149480093f4SDimitry Andric     case 'b':
4150480093f4SDimitry Andric     case 'B':
4151480093f4SDimitry Andric     case 'f':
4152480093f4SDimitry Andric       // Change the format for the currently selected item
4153480093f4SDimitry Andric       if (m_selected_row) {
4154480093f4SDimitry Andric         auto valobj_sp = m_selected_row->value.GetSP();
4155480093f4SDimitry Andric         if (valobj_sp)
4156480093f4SDimitry Andric           valobj_sp->SetFormat(FormatForChar(c));
4157480093f4SDimitry Andric       }
4158480093f4SDimitry Andric       return eKeyHandled;
4159480093f4SDimitry Andric 
4160480093f4SDimitry Andric     case 't':
4161480093f4SDimitry Andric       // Toggle showing type names
4162480093f4SDimitry Andric       g_options.show_types = !g_options.show_types;
4163480093f4SDimitry Andric       return eKeyHandled;
4164480093f4SDimitry Andric 
4165480093f4SDimitry Andric     case ',':
4166480093f4SDimitry Andric     case KEY_PPAGE:
4167480093f4SDimitry Andric       // Page up key
4168480093f4SDimitry Andric       if (m_first_visible_row > 0) {
4169480093f4SDimitry Andric         if (static_cast<int>(m_first_visible_row) > m_max_y)
4170480093f4SDimitry Andric           m_first_visible_row -= m_max_y;
4171480093f4SDimitry Andric         else
4172480093f4SDimitry Andric           m_first_visible_row = 0;
4173480093f4SDimitry Andric         m_selected_row_idx = m_first_visible_row;
4174480093f4SDimitry Andric       }
4175480093f4SDimitry Andric       return eKeyHandled;
4176480093f4SDimitry Andric 
4177480093f4SDimitry Andric     case '.':
4178480093f4SDimitry Andric     case KEY_NPAGE:
4179480093f4SDimitry Andric       // Page down key
4180480093f4SDimitry Andric       if (m_num_rows > static_cast<size_t>(m_max_y)) {
4181480093f4SDimitry Andric         if (m_first_visible_row + m_max_y < m_num_rows) {
4182480093f4SDimitry Andric           m_first_visible_row += m_max_y;
4183480093f4SDimitry Andric           m_selected_row_idx = m_first_visible_row;
4184480093f4SDimitry Andric         }
4185480093f4SDimitry Andric       }
4186480093f4SDimitry Andric       return eKeyHandled;
4187480093f4SDimitry Andric 
4188480093f4SDimitry Andric     case KEY_UP:
4189480093f4SDimitry Andric       if (m_selected_row_idx > 0)
4190480093f4SDimitry Andric         --m_selected_row_idx;
4191480093f4SDimitry Andric       return eKeyHandled;
4192480093f4SDimitry Andric 
4193480093f4SDimitry Andric     case KEY_DOWN:
4194480093f4SDimitry Andric       if (m_selected_row_idx + 1 < m_num_rows)
4195480093f4SDimitry Andric         ++m_selected_row_idx;
4196480093f4SDimitry Andric       return eKeyHandled;
4197480093f4SDimitry Andric 
4198480093f4SDimitry Andric     case KEY_RIGHT:
4199480093f4SDimitry Andric       if (m_selected_row) {
4200480093f4SDimitry Andric         if (!m_selected_row->expanded)
4201480093f4SDimitry Andric           m_selected_row->Expand();
4202480093f4SDimitry Andric       }
4203480093f4SDimitry Andric       return eKeyHandled;
4204480093f4SDimitry Andric 
4205480093f4SDimitry Andric     case KEY_LEFT:
4206480093f4SDimitry Andric       if (m_selected_row) {
4207480093f4SDimitry Andric         if (m_selected_row->expanded)
4208480093f4SDimitry Andric           m_selected_row->Unexpand();
4209480093f4SDimitry Andric         else if (m_selected_row->parent)
4210480093f4SDimitry Andric           m_selected_row_idx = m_selected_row->parent->row_idx;
4211480093f4SDimitry Andric       }
4212480093f4SDimitry Andric       return eKeyHandled;
4213480093f4SDimitry Andric 
4214480093f4SDimitry Andric     case ' ':
4215480093f4SDimitry Andric       // Toggle expansion state when SPACE is pressed
4216480093f4SDimitry Andric       if (m_selected_row) {
4217480093f4SDimitry Andric         if (m_selected_row->expanded)
4218480093f4SDimitry Andric           m_selected_row->Unexpand();
4219480093f4SDimitry Andric         else
4220480093f4SDimitry Andric           m_selected_row->Expand();
4221480093f4SDimitry Andric       }
4222480093f4SDimitry Andric       return eKeyHandled;
4223480093f4SDimitry Andric 
4224480093f4SDimitry Andric     case 'h':
4225480093f4SDimitry Andric       window.CreateHelpSubwindow();
4226480093f4SDimitry Andric       return eKeyHandled;
4227480093f4SDimitry Andric 
4228480093f4SDimitry Andric     default:
4229480093f4SDimitry Andric       break;
4230480093f4SDimitry Andric     }
4231480093f4SDimitry Andric     return eKeyNotHandled;
4232480093f4SDimitry Andric   }
4233480093f4SDimitry Andric 
4234480093f4SDimitry Andric protected:
4235480093f4SDimitry Andric   std::vector<Row> m_rows;
4236*5f7ddb14SDimitry Andric   Row *m_selected_row = nullptr;
4237*5f7ddb14SDimitry Andric   uint32_t m_selected_row_idx = 0;
4238*5f7ddb14SDimitry Andric   uint32_t m_first_visible_row = 0;
4239*5f7ddb14SDimitry Andric   uint32_t m_num_rows = 0;
4240480093f4SDimitry Andric   int m_min_x;
4241480093f4SDimitry Andric   int m_min_y;
4242*5f7ddb14SDimitry Andric   int m_max_x = 0;
4243*5f7ddb14SDimitry Andric   int m_max_y = 0;
4244480093f4SDimitry Andric 
FormatForChar(int c)4245480093f4SDimitry Andric   static Format FormatForChar(int c) {
4246480093f4SDimitry Andric     switch (c) {
4247480093f4SDimitry Andric     case 'x':
4248480093f4SDimitry Andric       return eFormatHex;
4249480093f4SDimitry Andric     case 'X':
4250480093f4SDimitry Andric       return eFormatHexUppercase;
4251480093f4SDimitry Andric     case 'o':
4252480093f4SDimitry Andric       return eFormatOctal;
4253480093f4SDimitry Andric     case 's':
4254480093f4SDimitry Andric       return eFormatCString;
4255480093f4SDimitry Andric     case 'u':
4256480093f4SDimitry Andric       return eFormatUnsigned;
4257480093f4SDimitry Andric     case 'd':
4258480093f4SDimitry Andric       return eFormatDecimal;
4259480093f4SDimitry Andric     case 'D':
4260480093f4SDimitry Andric       return eFormatDefault;
4261480093f4SDimitry Andric     case 'i':
4262480093f4SDimitry Andric       return eFormatInstruction;
4263480093f4SDimitry Andric     case 'A':
4264480093f4SDimitry Andric       return eFormatAddressInfo;
4265480093f4SDimitry Andric     case 'p':
4266480093f4SDimitry Andric       return eFormatPointer;
4267480093f4SDimitry Andric     case 'c':
4268480093f4SDimitry Andric       return eFormatChar;
4269480093f4SDimitry Andric     case 'b':
4270480093f4SDimitry Andric       return eFormatBinary;
4271480093f4SDimitry Andric     case 'B':
4272480093f4SDimitry Andric       return eFormatBytesWithASCII;
4273480093f4SDimitry Andric     case 'f':
4274480093f4SDimitry Andric       return eFormatFloat;
4275480093f4SDimitry Andric     }
4276480093f4SDimitry Andric     return eFormatDefault;
4277480093f4SDimitry Andric   }
4278480093f4SDimitry Andric 
DisplayRowObject(Window & window,Row & row,DisplayOptions & options,bool highlight,bool last_child)4279480093f4SDimitry Andric   bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options,
4280480093f4SDimitry Andric                         bool highlight, bool last_child) {
4281480093f4SDimitry Andric     ValueObject *valobj = row.value.GetSP().get();
4282480093f4SDimitry Andric 
4283480093f4SDimitry Andric     if (valobj == nullptr)
4284480093f4SDimitry Andric       return false;
4285480093f4SDimitry Andric 
4286480093f4SDimitry Andric     const char *type_name =
4287480093f4SDimitry Andric         options.show_types ? valobj->GetTypeName().GetCString() : nullptr;
4288480093f4SDimitry Andric     const char *name = valobj->GetName().GetCString();
4289480093f4SDimitry Andric     const char *value = valobj->GetValueAsCString();
4290480093f4SDimitry Andric     const char *summary = valobj->GetSummaryAsCString();
4291480093f4SDimitry Andric 
4292480093f4SDimitry Andric     window.MoveCursor(row.x, row.y);
4293480093f4SDimitry Andric 
4294480093f4SDimitry Andric     row.DrawTree(window);
4295480093f4SDimitry Andric 
4296480093f4SDimitry Andric     if (highlight)
4297480093f4SDimitry Andric       window.AttributeOn(A_REVERSE);
4298480093f4SDimitry Andric 
4299480093f4SDimitry Andric     if (type_name && type_name[0])
4300af732203SDimitry Andric       window.PrintfTruncated(1, "(%s) ", type_name);
4301480093f4SDimitry Andric 
4302480093f4SDimitry Andric     if (name && name[0])
4303af732203SDimitry Andric       window.PutCStringTruncated(1, name);
4304480093f4SDimitry Andric 
4305480093f4SDimitry Andric     attr_t changd_attr = 0;
4306480093f4SDimitry Andric     if (valobj->GetValueDidChange())
4307af732203SDimitry Andric       changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD;
4308480093f4SDimitry Andric 
4309480093f4SDimitry Andric     if (value && value[0]) {
4310af732203SDimitry Andric       window.PutCStringTruncated(1, " = ");
4311480093f4SDimitry Andric       if (changd_attr)
4312480093f4SDimitry Andric         window.AttributeOn(changd_attr);
4313af732203SDimitry Andric       window.PutCStringTruncated(1, value);
4314480093f4SDimitry Andric       if (changd_attr)
4315480093f4SDimitry Andric         window.AttributeOff(changd_attr);
4316480093f4SDimitry Andric     }
4317480093f4SDimitry Andric 
4318480093f4SDimitry Andric     if (summary && summary[0]) {
4319af732203SDimitry Andric       window.PutCStringTruncated(1, " ");
4320480093f4SDimitry Andric       if (changd_attr)
4321480093f4SDimitry Andric         window.AttributeOn(changd_attr);
4322af732203SDimitry Andric       window.PutCStringTruncated(1, summary);
4323480093f4SDimitry Andric       if (changd_attr)
4324480093f4SDimitry Andric         window.AttributeOff(changd_attr);
4325480093f4SDimitry Andric     }
4326480093f4SDimitry Andric 
4327480093f4SDimitry Andric     if (highlight)
4328480093f4SDimitry Andric       window.AttributeOff(A_REVERSE);
4329480093f4SDimitry Andric 
4330480093f4SDimitry Andric     return true;
4331480093f4SDimitry Andric   }
4332480093f4SDimitry Andric 
DisplayRows(Window & window,std::vector<Row> & rows,DisplayOptions & options)4333480093f4SDimitry Andric   void DisplayRows(Window &window, std::vector<Row> &rows,
4334480093f4SDimitry Andric                    DisplayOptions &options) {
4335480093f4SDimitry Andric     // >   0x25B7
4336480093f4SDimitry Andric     // \/  0x25BD
4337480093f4SDimitry Andric 
4338480093f4SDimitry Andric     bool window_is_active = window.IsActive();
4339480093f4SDimitry Andric     for (auto &row : rows) {
4340480093f4SDimitry Andric       const bool last_child = row.parent && &rows[rows.size() - 1] == &row;
4341480093f4SDimitry Andric       // Save the row index in each Row structure
4342480093f4SDimitry Andric       row.row_idx = m_num_rows;
4343480093f4SDimitry Andric       if ((m_num_rows >= m_first_visible_row) &&
4344480093f4SDimitry Andric           ((m_num_rows - m_first_visible_row) <
4345480093f4SDimitry Andric            static_cast<size_t>(NumVisibleRows()))) {
4346480093f4SDimitry Andric         row.x = m_min_x;
4347480093f4SDimitry Andric         row.y = m_num_rows - m_first_visible_row + 1;
4348480093f4SDimitry Andric         if (DisplayRowObject(window, row, options,
4349480093f4SDimitry Andric                              window_is_active &&
4350480093f4SDimitry Andric                                  m_num_rows == m_selected_row_idx,
4351480093f4SDimitry Andric                              last_child)) {
4352480093f4SDimitry Andric           ++m_num_rows;
4353480093f4SDimitry Andric         } else {
4354480093f4SDimitry Andric           row.x = 0;
4355480093f4SDimitry Andric           row.y = 0;
4356480093f4SDimitry Andric         }
4357480093f4SDimitry Andric       } else {
4358480093f4SDimitry Andric         row.x = 0;
4359480093f4SDimitry Andric         row.y = 0;
4360480093f4SDimitry Andric         ++m_num_rows;
4361480093f4SDimitry Andric       }
4362480093f4SDimitry Andric 
4363480093f4SDimitry Andric       auto &children = row.GetChildren();
4364480093f4SDimitry Andric       if (row.expanded && !children.empty()) {
4365480093f4SDimitry Andric         DisplayRows(window, children, options);
4366480093f4SDimitry Andric       }
4367480093f4SDimitry Andric     }
4368480093f4SDimitry Andric   }
4369480093f4SDimitry Andric 
CalculateTotalNumberRows(std::vector<Row> & rows)4370480093f4SDimitry Andric   int CalculateTotalNumberRows(std::vector<Row> &rows) {
4371480093f4SDimitry Andric     int row_count = 0;
4372480093f4SDimitry Andric     for (auto &row : rows) {
4373480093f4SDimitry Andric       ++row_count;
4374480093f4SDimitry Andric       if (row.expanded)
4375480093f4SDimitry Andric         row_count += CalculateTotalNumberRows(row.GetChildren());
4376480093f4SDimitry Andric     }
4377480093f4SDimitry Andric     return row_count;
4378480093f4SDimitry Andric   }
4379480093f4SDimitry Andric 
GetRowForRowIndexImpl(std::vector<Row> & rows,size_t & row_index)4380480093f4SDimitry Andric   static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) {
4381480093f4SDimitry Andric     for (auto &row : rows) {
4382480093f4SDimitry Andric       if (row_index == 0)
4383480093f4SDimitry Andric         return &row;
4384480093f4SDimitry Andric       else {
4385480093f4SDimitry Andric         --row_index;
4386480093f4SDimitry Andric         auto &children = row.GetChildren();
4387480093f4SDimitry Andric         if (row.expanded && !children.empty()) {
4388480093f4SDimitry Andric           Row *result = GetRowForRowIndexImpl(children, row_index);
4389480093f4SDimitry Andric           if (result)
4390480093f4SDimitry Andric             return result;
4391480093f4SDimitry Andric         }
4392480093f4SDimitry Andric       }
4393480093f4SDimitry Andric     }
4394480093f4SDimitry Andric     return nullptr;
4395480093f4SDimitry Andric   }
4396480093f4SDimitry Andric 
GetRowForRowIndex(size_t row_index)4397480093f4SDimitry Andric   Row *GetRowForRowIndex(size_t row_index) {
4398480093f4SDimitry Andric     return GetRowForRowIndexImpl(m_rows, row_index);
4399480093f4SDimitry Andric   }
4400480093f4SDimitry Andric 
NumVisibleRows() const4401480093f4SDimitry Andric   int NumVisibleRows() const { return m_max_y - m_min_y; }
4402480093f4SDimitry Andric 
4403480093f4SDimitry Andric   static DisplayOptions g_options;
4404480093f4SDimitry Andric };
4405480093f4SDimitry Andric 
4406480093f4SDimitry Andric class FrameVariablesWindowDelegate : public ValueObjectListDelegate {
4407480093f4SDimitry Andric public:
FrameVariablesWindowDelegate(Debugger & debugger)4408480093f4SDimitry Andric   FrameVariablesWindowDelegate(Debugger &debugger)
4409480093f4SDimitry Andric       : ValueObjectListDelegate(), m_debugger(debugger),
4410480093f4SDimitry Andric         m_frame_block(nullptr) {}
4411480093f4SDimitry Andric 
4412480093f4SDimitry Andric   ~FrameVariablesWindowDelegate() override = default;
4413480093f4SDimitry Andric 
WindowDelegateGetHelpText()4414480093f4SDimitry Andric   const char *WindowDelegateGetHelpText() override {
4415480093f4SDimitry Andric     return "Frame variable window keyboard shortcuts:";
4416480093f4SDimitry Andric   }
4417480093f4SDimitry Andric 
WindowDelegateDraw(Window & window,bool force)4418480093f4SDimitry Andric   bool WindowDelegateDraw(Window &window, bool force) override {
4419480093f4SDimitry Andric     ExecutionContext exe_ctx(
4420480093f4SDimitry Andric         m_debugger.GetCommandInterpreter().GetExecutionContext());
4421480093f4SDimitry Andric     Process *process = exe_ctx.GetProcessPtr();
4422480093f4SDimitry Andric     Block *frame_block = nullptr;
4423480093f4SDimitry Andric     StackFrame *frame = nullptr;
4424480093f4SDimitry Andric 
4425480093f4SDimitry Andric     if (process) {
4426480093f4SDimitry Andric       StateType state = process->GetState();
4427480093f4SDimitry Andric       if (StateIsStoppedState(state, true)) {
4428480093f4SDimitry Andric         frame = exe_ctx.GetFramePtr();
4429480093f4SDimitry Andric         if (frame)
4430480093f4SDimitry Andric           frame_block = frame->GetFrameBlock();
4431480093f4SDimitry Andric       } else if (StateIsRunningState(state)) {
4432480093f4SDimitry Andric         return true; // Don't do any updating when we are running
4433480093f4SDimitry Andric       }
4434480093f4SDimitry Andric     }
4435480093f4SDimitry Andric 
4436480093f4SDimitry Andric     ValueObjectList local_values;
4437480093f4SDimitry Andric     if (frame_block) {
4438480093f4SDimitry Andric       // Only update the variables if they have changed
4439480093f4SDimitry Andric       if (m_frame_block != frame_block) {
4440480093f4SDimitry Andric         m_frame_block = frame_block;
4441480093f4SDimitry Andric 
4442480093f4SDimitry Andric         VariableList *locals = frame->GetVariableList(true);
4443480093f4SDimitry Andric         if (locals) {
4444480093f4SDimitry Andric           const DynamicValueType use_dynamic = eDynamicDontRunTarget;
4445480093f4SDimitry Andric           for (const VariableSP &local_sp : *locals) {
4446480093f4SDimitry Andric             ValueObjectSP value_sp =
4447480093f4SDimitry Andric                 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic);
4448480093f4SDimitry Andric             if (value_sp) {
4449480093f4SDimitry Andric               ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue();
4450480093f4SDimitry Andric               if (synthetic_value_sp)
4451480093f4SDimitry Andric                 local_values.Append(synthetic_value_sp);
4452480093f4SDimitry Andric               else
4453480093f4SDimitry Andric                 local_values.Append(value_sp);
4454480093f4SDimitry Andric             }
4455480093f4SDimitry Andric           }
4456480093f4SDimitry Andric           // Update the values
4457480093f4SDimitry Andric           SetValues(local_values);
4458480093f4SDimitry Andric         }
4459480093f4SDimitry Andric       }
4460480093f4SDimitry Andric     } else {
4461480093f4SDimitry Andric       m_frame_block = nullptr;
4462480093f4SDimitry Andric       // Update the values with an empty list if there is no frame
4463480093f4SDimitry Andric       SetValues(local_values);
4464480093f4SDimitry Andric     }
4465480093f4SDimitry Andric 
4466480093f4SDimitry Andric     return ValueObjectListDelegate::WindowDelegateDraw(window, force);
4467480093f4SDimitry Andric   }
4468480093f4SDimitry Andric 
4469480093f4SDimitry Andric protected:
4470480093f4SDimitry Andric   Debugger &m_debugger;
4471480093f4SDimitry Andric   Block *m_frame_block;
4472480093f4SDimitry Andric };
4473480093f4SDimitry Andric 
4474480093f4SDimitry Andric class RegistersWindowDelegate : public ValueObjectListDelegate {
4475480093f4SDimitry Andric public:
RegistersWindowDelegate(Debugger & debugger)4476480093f4SDimitry Andric   RegistersWindowDelegate(Debugger &debugger)
4477480093f4SDimitry Andric       : ValueObjectListDelegate(), m_debugger(debugger) {}
4478480093f4SDimitry Andric 
4479480093f4SDimitry Andric   ~RegistersWindowDelegate() override = default;
4480480093f4SDimitry Andric 
WindowDelegateGetHelpText()4481480093f4SDimitry Andric   const char *WindowDelegateGetHelpText() override {
4482480093f4SDimitry Andric     return "Register window keyboard shortcuts:";
4483480093f4SDimitry Andric   }
4484480093f4SDimitry Andric 
WindowDelegateDraw(Window & window,bool force)4485480093f4SDimitry Andric   bool WindowDelegateDraw(Window &window, bool force) override {
4486480093f4SDimitry Andric     ExecutionContext exe_ctx(
4487480093f4SDimitry Andric         m_debugger.GetCommandInterpreter().GetExecutionContext());
4488480093f4SDimitry Andric     StackFrame *frame = exe_ctx.GetFramePtr();
4489480093f4SDimitry Andric 
4490480093f4SDimitry Andric     ValueObjectList value_list;
4491480093f4SDimitry Andric     if (frame) {
4492480093f4SDimitry Andric       if (frame->GetStackID() != m_stack_id) {
4493480093f4SDimitry Andric         m_stack_id = frame->GetStackID();
4494480093f4SDimitry Andric         RegisterContextSP reg_ctx(frame->GetRegisterContext());
4495480093f4SDimitry Andric         if (reg_ctx) {
4496480093f4SDimitry Andric           const uint32_t num_sets = reg_ctx->GetRegisterSetCount();
4497480093f4SDimitry Andric           for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) {
4498480093f4SDimitry Andric             value_list.Append(
4499480093f4SDimitry Andric                 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx));
4500480093f4SDimitry Andric           }
4501480093f4SDimitry Andric         }
4502480093f4SDimitry Andric         SetValues(value_list);
4503480093f4SDimitry Andric       }
4504480093f4SDimitry Andric     } else {
4505480093f4SDimitry Andric       Process *process = exe_ctx.GetProcessPtr();
4506480093f4SDimitry Andric       if (process && process->IsAlive())
4507480093f4SDimitry Andric         return true; // Don't do any updating if we are running
4508480093f4SDimitry Andric       else {
4509480093f4SDimitry Andric         // Update the values with an empty list if there is no process or the
4510480093f4SDimitry Andric         // process isn't alive anymore
4511480093f4SDimitry Andric         SetValues(value_list);
4512480093f4SDimitry Andric       }
4513480093f4SDimitry Andric     }
4514480093f4SDimitry Andric     return ValueObjectListDelegate::WindowDelegateDraw(window, force);
4515480093f4SDimitry Andric   }
4516480093f4SDimitry Andric 
4517480093f4SDimitry Andric protected:
4518480093f4SDimitry Andric   Debugger &m_debugger;
4519480093f4SDimitry Andric   StackID m_stack_id;
4520480093f4SDimitry Andric };
4521480093f4SDimitry Andric 
CursesKeyToCString(int ch)4522480093f4SDimitry Andric static const char *CursesKeyToCString(int ch) {
4523480093f4SDimitry Andric   static char g_desc[32];
4524480093f4SDimitry Andric   if (ch >= KEY_F0 && ch < KEY_F0 + 64) {
4525480093f4SDimitry Andric     snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0);
4526480093f4SDimitry Andric     return g_desc;
4527480093f4SDimitry Andric   }
4528480093f4SDimitry Andric   switch (ch) {
4529480093f4SDimitry Andric   case KEY_DOWN:
4530480093f4SDimitry Andric     return "down";
4531480093f4SDimitry Andric   case KEY_UP:
4532480093f4SDimitry Andric     return "up";
4533480093f4SDimitry Andric   case KEY_LEFT:
4534480093f4SDimitry Andric     return "left";
4535480093f4SDimitry Andric   case KEY_RIGHT:
4536480093f4SDimitry Andric     return "right";
4537480093f4SDimitry Andric   case KEY_HOME:
4538480093f4SDimitry Andric     return "home";
4539480093f4SDimitry Andric   case KEY_BACKSPACE:
4540480093f4SDimitry Andric     return "backspace";
4541480093f4SDimitry Andric   case KEY_DL:
4542480093f4SDimitry Andric     return "delete-line";
4543480093f4SDimitry Andric   case KEY_IL:
4544480093f4SDimitry Andric     return "insert-line";
4545480093f4SDimitry Andric   case KEY_DC:
4546480093f4SDimitry Andric     return "delete-char";
4547480093f4SDimitry Andric   case KEY_IC:
4548480093f4SDimitry Andric     return "insert-char";
4549480093f4SDimitry Andric   case KEY_CLEAR:
4550480093f4SDimitry Andric     return "clear";
4551480093f4SDimitry Andric   case KEY_EOS:
4552480093f4SDimitry Andric     return "clear-to-eos";
4553480093f4SDimitry Andric   case KEY_EOL:
4554480093f4SDimitry Andric     return "clear-to-eol";
4555480093f4SDimitry Andric   case KEY_SF:
4556480093f4SDimitry Andric     return "scroll-forward";
4557480093f4SDimitry Andric   case KEY_SR:
4558480093f4SDimitry Andric     return "scroll-backward";
4559480093f4SDimitry Andric   case KEY_NPAGE:
4560480093f4SDimitry Andric     return "page-down";
4561480093f4SDimitry Andric   case KEY_PPAGE:
4562480093f4SDimitry Andric     return "page-up";
4563480093f4SDimitry Andric   case KEY_STAB:
4564480093f4SDimitry Andric     return "set-tab";
4565480093f4SDimitry Andric   case KEY_CTAB:
4566480093f4SDimitry Andric     return "clear-tab";
4567480093f4SDimitry Andric   case KEY_CATAB:
4568480093f4SDimitry Andric     return "clear-all-tabs";
4569480093f4SDimitry Andric   case KEY_ENTER:
4570480093f4SDimitry Andric     return "enter";
4571480093f4SDimitry Andric   case KEY_PRINT:
4572480093f4SDimitry Andric     return "print";
4573480093f4SDimitry Andric   case KEY_LL:
4574480093f4SDimitry Andric     return "lower-left key";
4575480093f4SDimitry Andric   case KEY_A1:
4576480093f4SDimitry Andric     return "upper left of keypad";
4577480093f4SDimitry Andric   case KEY_A3:
4578480093f4SDimitry Andric     return "upper right of keypad";
4579480093f4SDimitry Andric   case KEY_B2:
4580480093f4SDimitry Andric     return "center of keypad";
4581480093f4SDimitry Andric   case KEY_C1:
4582480093f4SDimitry Andric     return "lower left of keypad";
4583480093f4SDimitry Andric   case KEY_C3:
4584480093f4SDimitry Andric     return "lower right of keypad";
4585480093f4SDimitry Andric   case KEY_BTAB:
4586480093f4SDimitry Andric     return "back-tab key";
4587480093f4SDimitry Andric   case KEY_BEG:
4588480093f4SDimitry Andric     return "begin key";
4589480093f4SDimitry Andric   case KEY_CANCEL:
4590480093f4SDimitry Andric     return "cancel key";
4591480093f4SDimitry Andric   case KEY_CLOSE:
4592480093f4SDimitry Andric     return "close key";
4593480093f4SDimitry Andric   case KEY_COMMAND:
4594480093f4SDimitry Andric     return "command key";
4595480093f4SDimitry Andric   case KEY_COPY:
4596480093f4SDimitry Andric     return "copy key";
4597480093f4SDimitry Andric   case KEY_CREATE:
4598480093f4SDimitry Andric     return "create key";
4599480093f4SDimitry Andric   case KEY_END:
4600480093f4SDimitry Andric     return "end key";
4601480093f4SDimitry Andric   case KEY_EXIT:
4602480093f4SDimitry Andric     return "exit key";
4603480093f4SDimitry Andric   case KEY_FIND:
4604480093f4SDimitry Andric     return "find key";
4605480093f4SDimitry Andric   case KEY_HELP:
4606480093f4SDimitry Andric     return "help key";
4607480093f4SDimitry Andric   case KEY_MARK:
4608480093f4SDimitry Andric     return "mark key";
4609480093f4SDimitry Andric   case KEY_MESSAGE:
4610480093f4SDimitry Andric     return "message key";
4611480093f4SDimitry Andric   case KEY_MOVE:
4612480093f4SDimitry Andric     return "move key";
4613480093f4SDimitry Andric   case KEY_NEXT:
4614480093f4SDimitry Andric     return "next key";
4615480093f4SDimitry Andric   case KEY_OPEN:
4616480093f4SDimitry Andric     return "open key";
4617480093f4SDimitry Andric   case KEY_OPTIONS:
4618480093f4SDimitry Andric     return "options key";
4619480093f4SDimitry Andric   case KEY_PREVIOUS:
4620480093f4SDimitry Andric     return "previous key";
4621480093f4SDimitry Andric   case KEY_REDO:
4622480093f4SDimitry Andric     return "redo key";
4623480093f4SDimitry Andric   case KEY_REFERENCE:
4624480093f4SDimitry Andric     return "reference key";
4625480093f4SDimitry Andric   case KEY_REFRESH:
4626480093f4SDimitry Andric     return "refresh key";
4627480093f4SDimitry Andric   case KEY_REPLACE:
4628480093f4SDimitry Andric     return "replace key";
4629480093f4SDimitry Andric   case KEY_RESTART:
4630480093f4SDimitry Andric     return "restart key";
4631480093f4SDimitry Andric   case KEY_RESUME:
4632480093f4SDimitry Andric     return "resume key";
4633480093f4SDimitry Andric   case KEY_SAVE:
4634480093f4SDimitry Andric     return "save key";
4635480093f4SDimitry Andric   case KEY_SBEG:
4636480093f4SDimitry Andric     return "shifted begin key";
4637480093f4SDimitry Andric   case KEY_SCANCEL:
4638480093f4SDimitry Andric     return "shifted cancel key";
4639480093f4SDimitry Andric   case KEY_SCOMMAND:
4640480093f4SDimitry Andric     return "shifted command key";
4641480093f4SDimitry Andric   case KEY_SCOPY:
4642480093f4SDimitry Andric     return "shifted copy key";
4643480093f4SDimitry Andric   case KEY_SCREATE:
4644480093f4SDimitry Andric     return "shifted create key";
4645480093f4SDimitry Andric   case KEY_SDC:
4646480093f4SDimitry Andric     return "shifted delete-character key";
4647480093f4SDimitry Andric   case KEY_SDL:
4648480093f4SDimitry Andric     return "shifted delete-line key";
4649480093f4SDimitry Andric   case KEY_SELECT:
4650480093f4SDimitry Andric     return "select key";
4651480093f4SDimitry Andric   case KEY_SEND:
4652480093f4SDimitry Andric     return "shifted end key";
4653480093f4SDimitry Andric   case KEY_SEOL:
4654480093f4SDimitry Andric     return "shifted clear-to-end-of-line key";
4655480093f4SDimitry Andric   case KEY_SEXIT:
4656480093f4SDimitry Andric     return "shifted exit key";
4657480093f4SDimitry Andric   case KEY_SFIND:
4658480093f4SDimitry Andric     return "shifted find key";
4659480093f4SDimitry Andric   case KEY_SHELP:
4660480093f4SDimitry Andric     return "shifted help key";
4661480093f4SDimitry Andric   case KEY_SHOME:
4662480093f4SDimitry Andric     return "shifted home key";
4663480093f4SDimitry Andric   case KEY_SIC:
4664480093f4SDimitry Andric     return "shifted insert-character key";
4665480093f4SDimitry Andric   case KEY_SLEFT:
4666480093f4SDimitry Andric     return "shifted left-arrow key";
4667480093f4SDimitry Andric   case KEY_SMESSAGE:
4668480093f4SDimitry Andric     return "shifted message key";
4669480093f4SDimitry Andric   case KEY_SMOVE:
4670480093f4SDimitry Andric     return "shifted move key";
4671480093f4SDimitry Andric   case KEY_SNEXT:
4672480093f4SDimitry Andric     return "shifted next key";
4673480093f4SDimitry Andric   case KEY_SOPTIONS:
4674480093f4SDimitry Andric     return "shifted options key";
4675480093f4SDimitry Andric   case KEY_SPREVIOUS:
4676480093f4SDimitry Andric     return "shifted previous key";
4677480093f4SDimitry Andric   case KEY_SPRINT:
4678480093f4SDimitry Andric     return "shifted print key";
4679480093f4SDimitry Andric   case KEY_SREDO:
4680480093f4SDimitry Andric     return "shifted redo key";
4681480093f4SDimitry Andric   case KEY_SREPLACE:
4682480093f4SDimitry Andric     return "shifted replace key";
4683480093f4SDimitry Andric   case KEY_SRIGHT:
4684480093f4SDimitry Andric     return "shifted right-arrow key";
4685480093f4SDimitry Andric   case KEY_SRSUME:
4686480093f4SDimitry Andric     return "shifted resume key";
4687480093f4SDimitry Andric   case KEY_SSAVE:
4688480093f4SDimitry Andric     return "shifted save key";
4689480093f4SDimitry Andric   case KEY_SSUSPEND:
4690480093f4SDimitry Andric     return "shifted suspend key";
4691480093f4SDimitry Andric   case KEY_SUNDO:
4692480093f4SDimitry Andric     return "shifted undo key";
4693480093f4SDimitry Andric   case KEY_SUSPEND:
4694480093f4SDimitry Andric     return "suspend key";
4695480093f4SDimitry Andric   case KEY_UNDO:
4696480093f4SDimitry Andric     return "undo key";
4697480093f4SDimitry Andric   case KEY_MOUSE:
4698480093f4SDimitry Andric     return "Mouse event has occurred";
4699480093f4SDimitry Andric   case KEY_RESIZE:
4700480093f4SDimitry Andric     return "Terminal resize event";
4701480093f4SDimitry Andric #ifdef KEY_EVENT
4702480093f4SDimitry Andric   case KEY_EVENT:
4703480093f4SDimitry Andric     return "We were interrupted by an event";
4704480093f4SDimitry Andric #endif
4705480093f4SDimitry Andric   case KEY_RETURN:
4706480093f4SDimitry Andric     return "return";
4707480093f4SDimitry Andric   case ' ':
4708480093f4SDimitry Andric     return "space";
4709480093f4SDimitry Andric   case '\t':
4710480093f4SDimitry Andric     return "tab";
4711480093f4SDimitry Andric   case KEY_ESCAPE:
4712480093f4SDimitry Andric     return "escape";
4713480093f4SDimitry Andric   default:
47145ffd83dbSDimitry Andric     if (llvm::isPrint(ch))
4715480093f4SDimitry Andric       snprintf(g_desc, sizeof(g_desc), "%c", ch);
4716480093f4SDimitry Andric     else
4717480093f4SDimitry Andric       snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch);
4718480093f4SDimitry Andric     return g_desc;
4719480093f4SDimitry Andric   }
4720480093f4SDimitry Andric   return nullptr;
4721480093f4SDimitry Andric }
4722480093f4SDimitry Andric 
HelpDialogDelegate(const char * text,KeyHelp * key_help_array)4723480093f4SDimitry Andric HelpDialogDelegate::HelpDialogDelegate(const char *text,
4724480093f4SDimitry Andric                                        KeyHelp *key_help_array)
4725480093f4SDimitry Andric     : m_text(), m_first_visible_line(0) {
4726480093f4SDimitry Andric   if (text && text[0]) {
4727480093f4SDimitry Andric     m_text.SplitIntoLines(text);
4728480093f4SDimitry Andric     m_text.AppendString("");
4729480093f4SDimitry Andric   }
4730480093f4SDimitry Andric   if (key_help_array) {
4731480093f4SDimitry Andric     for (KeyHelp *key = key_help_array; key->ch; ++key) {
4732480093f4SDimitry Andric       StreamString key_description;
4733480093f4SDimitry Andric       key_description.Printf("%10s - %s", CursesKeyToCString(key->ch),
4734480093f4SDimitry Andric                              key->description);
4735480093f4SDimitry Andric       m_text.AppendString(key_description.GetString());
4736480093f4SDimitry Andric     }
4737480093f4SDimitry Andric   }
4738480093f4SDimitry Andric }
4739480093f4SDimitry Andric 
4740480093f4SDimitry Andric HelpDialogDelegate::~HelpDialogDelegate() = default;
4741480093f4SDimitry Andric 
WindowDelegateDraw(Window & window,bool force)4742480093f4SDimitry Andric bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) {
4743480093f4SDimitry Andric   window.Erase();
4744480093f4SDimitry Andric   const int window_height = window.GetHeight();
4745480093f4SDimitry Andric   int x = 2;
4746480093f4SDimitry Andric   int y = 1;
4747480093f4SDimitry Andric   const int min_y = y;
4748480093f4SDimitry Andric   const int max_y = window_height - 1 - y;
4749480093f4SDimitry Andric   const size_t num_visible_lines = max_y - min_y + 1;
4750480093f4SDimitry Andric   const size_t num_lines = m_text.GetSize();
4751480093f4SDimitry Andric   const char *bottom_message;
4752480093f4SDimitry Andric   if (num_lines <= num_visible_lines)
4753480093f4SDimitry Andric     bottom_message = "Press any key to exit";
4754480093f4SDimitry Andric   else
4755480093f4SDimitry Andric     bottom_message = "Use arrows to scroll, any other key to exit";
4756480093f4SDimitry Andric   window.DrawTitleBox(window.GetName(), bottom_message);
4757480093f4SDimitry Andric   while (y <= max_y) {
4758480093f4SDimitry Andric     window.MoveCursor(x, y);
4759480093f4SDimitry Andric     window.PutCStringTruncated(
4760af732203SDimitry Andric         1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y));
4761480093f4SDimitry Andric     ++y;
4762480093f4SDimitry Andric   }
4763480093f4SDimitry Andric   return true;
4764480093f4SDimitry Andric }
4765480093f4SDimitry Andric 
WindowDelegateHandleChar(Window & window,int key)4766480093f4SDimitry Andric HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window,
4767480093f4SDimitry Andric                                                               int key) {
4768480093f4SDimitry Andric   bool done = false;
4769480093f4SDimitry Andric   const size_t num_lines = m_text.GetSize();
4770480093f4SDimitry Andric   const size_t num_visible_lines = window.GetHeight() - 2;
4771480093f4SDimitry Andric 
4772480093f4SDimitry Andric   if (num_lines <= num_visible_lines) {
4773480093f4SDimitry Andric     done = true;
4774480093f4SDimitry Andric     // If we have all lines visible and don't need scrolling, then any key
4775480093f4SDimitry Andric     // press will cause us to exit
4776480093f4SDimitry Andric   } else {
4777480093f4SDimitry Andric     switch (key) {
4778480093f4SDimitry Andric     case KEY_UP:
4779480093f4SDimitry Andric       if (m_first_visible_line > 0)
4780480093f4SDimitry Andric         --m_first_visible_line;
4781480093f4SDimitry Andric       break;
4782480093f4SDimitry Andric 
4783480093f4SDimitry Andric     case KEY_DOWN:
4784480093f4SDimitry Andric       if (m_first_visible_line + num_visible_lines < num_lines)
4785480093f4SDimitry Andric         ++m_first_visible_line;
4786480093f4SDimitry Andric       break;
4787480093f4SDimitry Andric 
4788480093f4SDimitry Andric     case KEY_PPAGE:
4789480093f4SDimitry Andric     case ',':
4790480093f4SDimitry Andric       if (m_first_visible_line > 0) {
4791480093f4SDimitry Andric         if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines)
4792480093f4SDimitry Andric           m_first_visible_line -= num_visible_lines;
4793480093f4SDimitry Andric         else
4794480093f4SDimitry Andric           m_first_visible_line = 0;
4795480093f4SDimitry Andric       }
4796480093f4SDimitry Andric       break;
4797480093f4SDimitry Andric 
4798480093f4SDimitry Andric     case KEY_NPAGE:
4799480093f4SDimitry Andric     case '.':
4800480093f4SDimitry Andric       if (m_first_visible_line + num_visible_lines < num_lines) {
4801480093f4SDimitry Andric         m_first_visible_line += num_visible_lines;
4802480093f4SDimitry Andric         if (static_cast<size_t>(m_first_visible_line) > num_lines)
4803480093f4SDimitry Andric           m_first_visible_line = num_lines - num_visible_lines;
4804480093f4SDimitry Andric       }
4805480093f4SDimitry Andric       break;
4806480093f4SDimitry Andric 
4807480093f4SDimitry Andric     default:
4808480093f4SDimitry Andric       done = true;
4809480093f4SDimitry Andric       break;
4810480093f4SDimitry Andric     }
4811480093f4SDimitry Andric   }
4812480093f4SDimitry Andric   if (done)
4813480093f4SDimitry Andric     window.GetParent()->RemoveSubWindow(&window);
4814480093f4SDimitry Andric   return eKeyHandled;
4815480093f4SDimitry Andric }
4816480093f4SDimitry Andric 
4817480093f4SDimitry Andric class ApplicationDelegate : public WindowDelegate, public MenuDelegate {
4818480093f4SDimitry Andric public:
4819480093f4SDimitry Andric   enum {
4820480093f4SDimitry Andric     eMenuID_LLDB = 1,
4821480093f4SDimitry Andric     eMenuID_LLDBAbout,
4822480093f4SDimitry Andric     eMenuID_LLDBExit,
4823480093f4SDimitry Andric 
4824480093f4SDimitry Andric     eMenuID_Target,
4825480093f4SDimitry Andric     eMenuID_TargetCreate,
4826480093f4SDimitry Andric     eMenuID_TargetDelete,
4827480093f4SDimitry Andric 
4828480093f4SDimitry Andric     eMenuID_Process,
4829480093f4SDimitry Andric     eMenuID_ProcessAttach,
4830af732203SDimitry Andric     eMenuID_ProcessDetachResume,
4831af732203SDimitry Andric     eMenuID_ProcessDetachSuspended,
4832480093f4SDimitry Andric     eMenuID_ProcessLaunch,
4833480093f4SDimitry Andric     eMenuID_ProcessContinue,
4834480093f4SDimitry Andric     eMenuID_ProcessHalt,
4835480093f4SDimitry Andric     eMenuID_ProcessKill,
4836480093f4SDimitry Andric 
4837480093f4SDimitry Andric     eMenuID_Thread,
4838480093f4SDimitry Andric     eMenuID_ThreadStepIn,
4839480093f4SDimitry Andric     eMenuID_ThreadStepOver,
4840480093f4SDimitry Andric     eMenuID_ThreadStepOut,
4841480093f4SDimitry Andric 
4842480093f4SDimitry Andric     eMenuID_View,
4843480093f4SDimitry Andric     eMenuID_ViewBacktrace,
4844480093f4SDimitry Andric     eMenuID_ViewRegisters,
4845480093f4SDimitry Andric     eMenuID_ViewSource,
4846480093f4SDimitry Andric     eMenuID_ViewVariables,
4847480093f4SDimitry Andric 
4848480093f4SDimitry Andric     eMenuID_Help,
4849480093f4SDimitry Andric     eMenuID_HelpGUIHelp
4850480093f4SDimitry Andric   };
4851480093f4SDimitry Andric 
ApplicationDelegate(Application & app,Debugger & debugger)4852480093f4SDimitry Andric   ApplicationDelegate(Application &app, Debugger &debugger)
4853480093f4SDimitry Andric       : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {}
4854480093f4SDimitry Andric 
4855480093f4SDimitry Andric   ~ApplicationDelegate() override = default;
4856480093f4SDimitry Andric 
WindowDelegateDraw(Window & window,bool force)4857480093f4SDimitry Andric   bool WindowDelegateDraw(Window &window, bool force) override {
4858480093f4SDimitry Andric     return false; // Drawing not handled, let standard window drawing happen
4859480093f4SDimitry Andric   }
4860480093f4SDimitry Andric 
WindowDelegateHandleChar(Window & window,int key)4861480093f4SDimitry Andric   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
4862480093f4SDimitry Andric     switch (key) {
4863480093f4SDimitry Andric     case '\t':
4864480093f4SDimitry Andric       window.SelectNextWindowAsActive();
4865480093f4SDimitry Andric       return eKeyHandled;
4866480093f4SDimitry Andric 
4867*5f7ddb14SDimitry Andric     case KEY_SHIFT_TAB:
4868af732203SDimitry Andric       window.SelectPreviousWindowAsActive();
4869af732203SDimitry Andric       return eKeyHandled;
4870af732203SDimitry Andric 
4871480093f4SDimitry Andric     case 'h':
4872480093f4SDimitry Andric       window.CreateHelpSubwindow();
4873480093f4SDimitry Andric       return eKeyHandled;
4874480093f4SDimitry Andric 
4875480093f4SDimitry Andric     case KEY_ESCAPE:
4876480093f4SDimitry Andric       return eQuitApplication;
4877480093f4SDimitry Andric 
4878480093f4SDimitry Andric     default:
4879480093f4SDimitry Andric       break;
4880480093f4SDimitry Andric     }
4881480093f4SDimitry Andric     return eKeyNotHandled;
4882480093f4SDimitry Andric   }
4883480093f4SDimitry Andric 
WindowDelegateGetHelpText()4884480093f4SDimitry Andric   const char *WindowDelegateGetHelpText() override {
4885480093f4SDimitry Andric     return "Welcome to the LLDB curses GUI.\n\n"
4886480093f4SDimitry Andric            "Press the TAB key to change the selected view.\n"
4887480093f4SDimitry Andric            "Each view has its own keyboard shortcuts, press 'h' to open a "
4888480093f4SDimitry Andric            "dialog to display them.\n\n"
4889480093f4SDimitry Andric            "Common key bindings for all views:";
4890480093f4SDimitry Andric   }
4891480093f4SDimitry Andric 
WindowDelegateGetKeyHelp()4892480093f4SDimitry Andric   KeyHelp *WindowDelegateGetKeyHelp() override {
4893480093f4SDimitry Andric     static curses::KeyHelp g_source_view_key_help[] = {
4894480093f4SDimitry Andric         {'\t', "Select next view"},
4895af732203SDimitry Andric         {KEY_BTAB, "Select previous view"},
4896480093f4SDimitry Andric         {'h', "Show help dialog with view specific key bindings"},
4897480093f4SDimitry Andric         {',', "Page up"},
4898480093f4SDimitry Andric         {'.', "Page down"},
4899480093f4SDimitry Andric         {KEY_UP, "Select previous"},
4900480093f4SDimitry Andric         {KEY_DOWN, "Select next"},
4901480093f4SDimitry Andric         {KEY_LEFT, "Unexpand or select parent"},
4902480093f4SDimitry Andric         {KEY_RIGHT, "Expand"},
4903480093f4SDimitry Andric         {KEY_PPAGE, "Page up"},
4904480093f4SDimitry Andric         {KEY_NPAGE, "Page down"},
4905480093f4SDimitry Andric         {'\0', nullptr}};
4906480093f4SDimitry Andric     return g_source_view_key_help;
4907480093f4SDimitry Andric   }
4908480093f4SDimitry Andric 
MenuDelegateAction(Menu & menu)4909480093f4SDimitry Andric   MenuActionResult MenuDelegateAction(Menu &menu) override {
4910480093f4SDimitry Andric     switch (menu.GetIdentifier()) {
4911480093f4SDimitry Andric     case eMenuID_ThreadStepIn: {
4912480093f4SDimitry Andric       ExecutionContext exe_ctx =
4913480093f4SDimitry Andric           m_debugger.GetCommandInterpreter().GetExecutionContext();
4914480093f4SDimitry Andric       if (exe_ctx.HasThreadScope()) {
4915480093f4SDimitry Andric         Process *process = exe_ctx.GetProcessPtr();
4916480093f4SDimitry Andric         if (process && process->IsAlive() &&
4917480093f4SDimitry Andric             StateIsStoppedState(process->GetState(), true))
4918480093f4SDimitry Andric           exe_ctx.GetThreadRef().StepIn(true);
4919480093f4SDimitry Andric       }
4920480093f4SDimitry Andric     }
4921480093f4SDimitry Andric       return MenuActionResult::Handled;
4922480093f4SDimitry Andric 
4923480093f4SDimitry Andric     case eMenuID_ThreadStepOut: {
4924480093f4SDimitry Andric       ExecutionContext exe_ctx =
4925480093f4SDimitry Andric           m_debugger.GetCommandInterpreter().GetExecutionContext();
4926480093f4SDimitry Andric       if (exe_ctx.HasThreadScope()) {
4927480093f4SDimitry Andric         Process *process = exe_ctx.GetProcessPtr();
4928480093f4SDimitry Andric         if (process && process->IsAlive() &&
4929480093f4SDimitry Andric             StateIsStoppedState(process->GetState(), true))
4930480093f4SDimitry Andric           exe_ctx.GetThreadRef().StepOut();
4931480093f4SDimitry Andric       }
4932480093f4SDimitry Andric     }
4933480093f4SDimitry Andric       return MenuActionResult::Handled;
4934480093f4SDimitry Andric 
4935480093f4SDimitry Andric     case eMenuID_ThreadStepOver: {
4936480093f4SDimitry Andric       ExecutionContext exe_ctx =
4937480093f4SDimitry Andric           m_debugger.GetCommandInterpreter().GetExecutionContext();
4938480093f4SDimitry Andric       if (exe_ctx.HasThreadScope()) {
4939480093f4SDimitry Andric         Process *process = exe_ctx.GetProcessPtr();
4940480093f4SDimitry Andric         if (process && process->IsAlive() &&
4941480093f4SDimitry Andric             StateIsStoppedState(process->GetState(), true))
4942480093f4SDimitry Andric           exe_ctx.GetThreadRef().StepOver(true);
4943480093f4SDimitry Andric       }
4944480093f4SDimitry Andric     }
4945480093f4SDimitry Andric       return MenuActionResult::Handled;
4946480093f4SDimitry Andric 
4947*5f7ddb14SDimitry Andric     case eMenuID_ProcessAttach: {
4948*5f7ddb14SDimitry Andric       WindowSP main_window_sp = m_app.GetMainWindow();
4949*5f7ddb14SDimitry Andric       FormDelegateSP form_delegate_sp = FormDelegateSP(
4950*5f7ddb14SDimitry Andric           new ProcessAttachFormDelegate(m_debugger, main_window_sp));
4951*5f7ddb14SDimitry Andric       Rect bounds = main_window_sp->GetCenteredRect(80, 22);
4952*5f7ddb14SDimitry Andric       WindowSP form_window_sp = main_window_sp->CreateSubWindow(
4953*5f7ddb14SDimitry Andric           form_delegate_sp->GetName().c_str(), bounds, true);
4954*5f7ddb14SDimitry Andric       WindowDelegateSP window_delegate_sp =
4955*5f7ddb14SDimitry Andric           WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
4956*5f7ddb14SDimitry Andric       form_window_sp->SetDelegate(window_delegate_sp);
4957*5f7ddb14SDimitry Andric       return MenuActionResult::Handled;
4958*5f7ddb14SDimitry Andric     }
4959*5f7ddb14SDimitry Andric 
4960480093f4SDimitry Andric     case eMenuID_ProcessContinue: {
4961480093f4SDimitry Andric       ExecutionContext exe_ctx =
4962480093f4SDimitry Andric           m_debugger.GetCommandInterpreter().GetExecutionContext();
4963480093f4SDimitry Andric       if (exe_ctx.HasProcessScope()) {
4964480093f4SDimitry Andric         Process *process = exe_ctx.GetProcessPtr();
4965480093f4SDimitry Andric         if (process && process->IsAlive() &&
4966480093f4SDimitry Andric             StateIsStoppedState(process->GetState(), true))
4967480093f4SDimitry Andric           process->Resume();
4968480093f4SDimitry Andric       }
4969480093f4SDimitry Andric     }
4970480093f4SDimitry Andric       return MenuActionResult::Handled;
4971480093f4SDimitry Andric 
4972480093f4SDimitry Andric     case eMenuID_ProcessKill: {
4973480093f4SDimitry Andric       ExecutionContext exe_ctx =
4974480093f4SDimitry Andric           m_debugger.GetCommandInterpreter().GetExecutionContext();
4975480093f4SDimitry Andric       if (exe_ctx.HasProcessScope()) {
4976480093f4SDimitry Andric         Process *process = exe_ctx.GetProcessPtr();
4977480093f4SDimitry Andric         if (process && process->IsAlive())
4978480093f4SDimitry Andric           process->Destroy(false);
4979480093f4SDimitry Andric       }
4980480093f4SDimitry Andric     }
4981480093f4SDimitry Andric       return MenuActionResult::Handled;
4982480093f4SDimitry Andric 
4983480093f4SDimitry Andric     case eMenuID_ProcessHalt: {
4984480093f4SDimitry Andric       ExecutionContext exe_ctx =
4985480093f4SDimitry Andric           m_debugger.GetCommandInterpreter().GetExecutionContext();
4986480093f4SDimitry Andric       if (exe_ctx.HasProcessScope()) {
4987480093f4SDimitry Andric         Process *process = exe_ctx.GetProcessPtr();
4988480093f4SDimitry Andric         if (process && process->IsAlive())
4989480093f4SDimitry Andric           process->Halt();
4990480093f4SDimitry Andric       }
4991480093f4SDimitry Andric     }
4992480093f4SDimitry Andric       return MenuActionResult::Handled;
4993480093f4SDimitry Andric 
4994af732203SDimitry Andric     case eMenuID_ProcessDetachResume:
4995af732203SDimitry Andric     case eMenuID_ProcessDetachSuspended: {
4996480093f4SDimitry Andric       ExecutionContext exe_ctx =
4997480093f4SDimitry Andric           m_debugger.GetCommandInterpreter().GetExecutionContext();
4998480093f4SDimitry Andric       if (exe_ctx.HasProcessScope()) {
4999480093f4SDimitry Andric         Process *process = exe_ctx.GetProcessPtr();
5000480093f4SDimitry Andric         if (process && process->IsAlive())
5001af732203SDimitry Andric           process->Detach(menu.GetIdentifier() ==
5002af732203SDimitry Andric                           eMenuID_ProcessDetachSuspended);
5003480093f4SDimitry Andric       }
5004480093f4SDimitry Andric     }
5005480093f4SDimitry Andric       return MenuActionResult::Handled;
5006480093f4SDimitry Andric 
5007480093f4SDimitry Andric     case eMenuID_Process: {
5008480093f4SDimitry Andric       // Populate the menu with all of the threads if the process is stopped
5009480093f4SDimitry Andric       // when the Process menu gets selected and is about to display its
5010480093f4SDimitry Andric       // submenu.
5011480093f4SDimitry Andric       Menus &submenus = menu.GetSubmenus();
5012480093f4SDimitry Andric       ExecutionContext exe_ctx =
5013480093f4SDimitry Andric           m_debugger.GetCommandInterpreter().GetExecutionContext();
5014480093f4SDimitry Andric       Process *process = exe_ctx.GetProcessPtr();
5015480093f4SDimitry Andric       if (process && process->IsAlive() &&
5016480093f4SDimitry Andric           StateIsStoppedState(process->GetState(), true)) {
5017480093f4SDimitry Andric         if (submenus.size() == 7)
5018480093f4SDimitry Andric           menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
5019480093f4SDimitry Andric         else if (submenus.size() > 8)
5020480093f4SDimitry Andric           submenus.erase(submenus.begin() + 8, submenus.end());
5021480093f4SDimitry Andric 
5022480093f4SDimitry Andric         ThreadList &threads = process->GetThreadList();
5023480093f4SDimitry Andric         std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
5024480093f4SDimitry Andric         size_t num_threads = threads.GetSize();
5025480093f4SDimitry Andric         for (size_t i = 0; i < num_threads; ++i) {
5026480093f4SDimitry Andric           ThreadSP thread_sp = threads.GetThreadAtIndex(i);
5027480093f4SDimitry Andric           char menu_char = '\0';
5028480093f4SDimitry Andric           if (i < 9)
5029480093f4SDimitry Andric             menu_char = '1' + i;
5030480093f4SDimitry Andric           StreamString thread_menu_title;
5031480093f4SDimitry Andric           thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID());
5032480093f4SDimitry Andric           const char *thread_name = thread_sp->GetName();
5033480093f4SDimitry Andric           if (thread_name && thread_name[0])
5034480093f4SDimitry Andric             thread_menu_title.Printf(" %s", thread_name);
5035480093f4SDimitry Andric           else {
5036480093f4SDimitry Andric             const char *queue_name = thread_sp->GetQueueName();
5037480093f4SDimitry Andric             if (queue_name && queue_name[0])
5038480093f4SDimitry Andric               thread_menu_title.Printf(" %s", queue_name);
5039480093f4SDimitry Andric           }
5040480093f4SDimitry Andric           menu.AddSubmenu(
5041480093f4SDimitry Andric               MenuSP(new Menu(thread_menu_title.GetString().str().c_str(),
5042480093f4SDimitry Andric                               nullptr, menu_char, thread_sp->GetID())));
5043480093f4SDimitry Andric         }
5044480093f4SDimitry Andric       } else if (submenus.size() > 7) {
5045480093f4SDimitry Andric         // Remove the separator and any other thread submenu items that were
5046480093f4SDimitry Andric         // previously added
5047480093f4SDimitry Andric         submenus.erase(submenus.begin() + 7, submenus.end());
5048480093f4SDimitry Andric       }
5049480093f4SDimitry Andric       // Since we are adding and removing items we need to recalculate the name
5050480093f4SDimitry Andric       // lengths
5051480093f4SDimitry Andric       menu.RecalculateNameLengths();
5052480093f4SDimitry Andric     }
5053480093f4SDimitry Andric       return MenuActionResult::Handled;
5054480093f4SDimitry Andric 
5055480093f4SDimitry Andric     case eMenuID_ViewVariables: {
5056480093f4SDimitry Andric       WindowSP main_window_sp = m_app.GetMainWindow();
5057480093f4SDimitry Andric       WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
5058480093f4SDimitry Andric       WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
5059480093f4SDimitry Andric       WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
5060480093f4SDimitry Andric       const Rect source_bounds = source_window_sp->GetBounds();
5061480093f4SDimitry Andric 
5062480093f4SDimitry Andric       if (variables_window_sp) {
5063480093f4SDimitry Andric         const Rect variables_bounds = variables_window_sp->GetBounds();
5064480093f4SDimitry Andric 
5065480093f4SDimitry Andric         main_window_sp->RemoveSubWindow(variables_window_sp.get());
5066480093f4SDimitry Andric 
5067480093f4SDimitry Andric         if (registers_window_sp) {
5068480093f4SDimitry Andric           // We have a registers window, so give all the area back to the
5069480093f4SDimitry Andric           // registers window
5070480093f4SDimitry Andric           Rect registers_bounds = variables_bounds;
5071480093f4SDimitry Andric           registers_bounds.size.width = source_bounds.size.width;
5072480093f4SDimitry Andric           registers_window_sp->SetBounds(registers_bounds);
5073480093f4SDimitry Andric         } else {
5074480093f4SDimitry Andric           // We have no registers window showing so give the bottom area back
5075480093f4SDimitry Andric           // to the source view
5076480093f4SDimitry Andric           source_window_sp->Resize(source_bounds.size.width,
5077480093f4SDimitry Andric                                    source_bounds.size.height +
5078480093f4SDimitry Andric                                        variables_bounds.size.height);
5079480093f4SDimitry Andric         }
5080480093f4SDimitry Andric       } else {
5081480093f4SDimitry Andric         Rect new_variables_rect;
5082480093f4SDimitry Andric         if (registers_window_sp) {
5083480093f4SDimitry Andric           // We have a registers window so split the area of the registers
5084480093f4SDimitry Andric           // window into two columns where the left hand side will be the
5085480093f4SDimitry Andric           // variables and the right hand side will be the registers
5086480093f4SDimitry Andric           const Rect variables_bounds = registers_window_sp->GetBounds();
5087480093f4SDimitry Andric           Rect new_registers_rect;
5088480093f4SDimitry Andric           variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect,
5089480093f4SDimitry Andric                                                    new_registers_rect);
5090480093f4SDimitry Andric           registers_window_sp->SetBounds(new_registers_rect);
5091480093f4SDimitry Andric         } else {
5092af732203SDimitry Andric           // No registers window, grab the bottom part of the source window
5093480093f4SDimitry Andric           Rect new_source_rect;
5094480093f4SDimitry Andric           source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
5095480093f4SDimitry Andric                                                   new_variables_rect);
5096480093f4SDimitry Andric           source_window_sp->SetBounds(new_source_rect);
5097480093f4SDimitry Andric         }
5098480093f4SDimitry Andric         WindowSP new_window_sp = main_window_sp->CreateSubWindow(
5099480093f4SDimitry Andric             "Variables", new_variables_rect, false);
5100480093f4SDimitry Andric         new_window_sp->SetDelegate(
5101480093f4SDimitry Andric             WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
5102480093f4SDimitry Andric       }
5103480093f4SDimitry Andric       touchwin(stdscr);
5104480093f4SDimitry Andric     }
5105480093f4SDimitry Andric       return MenuActionResult::Handled;
5106480093f4SDimitry Andric 
5107480093f4SDimitry Andric     case eMenuID_ViewRegisters: {
5108480093f4SDimitry Andric       WindowSP main_window_sp = m_app.GetMainWindow();
5109480093f4SDimitry Andric       WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
5110480093f4SDimitry Andric       WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
5111480093f4SDimitry Andric       WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
5112480093f4SDimitry Andric       const Rect source_bounds = source_window_sp->GetBounds();
5113480093f4SDimitry Andric 
5114480093f4SDimitry Andric       if (registers_window_sp) {
5115480093f4SDimitry Andric         if (variables_window_sp) {
5116480093f4SDimitry Andric           const Rect variables_bounds = variables_window_sp->GetBounds();
5117480093f4SDimitry Andric 
5118480093f4SDimitry Andric           // We have a variables window, so give all the area back to the
5119480093f4SDimitry Andric           // variables window
5120480093f4SDimitry Andric           variables_window_sp->Resize(variables_bounds.size.width +
5121480093f4SDimitry Andric                                           registers_window_sp->GetWidth(),
5122480093f4SDimitry Andric                                       variables_bounds.size.height);
5123480093f4SDimitry Andric         } else {
5124480093f4SDimitry Andric           // We have no variables window showing so give the bottom area back
5125480093f4SDimitry Andric           // to the source view
5126480093f4SDimitry Andric           source_window_sp->Resize(source_bounds.size.width,
5127480093f4SDimitry Andric                                    source_bounds.size.height +
5128480093f4SDimitry Andric                                        registers_window_sp->GetHeight());
5129480093f4SDimitry Andric         }
5130480093f4SDimitry Andric         main_window_sp->RemoveSubWindow(registers_window_sp.get());
5131480093f4SDimitry Andric       } else {
5132480093f4SDimitry Andric         Rect new_regs_rect;
5133480093f4SDimitry Andric         if (variables_window_sp) {
5134480093f4SDimitry Andric           // We have a variables window, split it into two columns where the
5135480093f4SDimitry Andric           // left hand side will be the variables and the right hand side will
5136480093f4SDimitry Andric           // be the registers
5137480093f4SDimitry Andric           const Rect variables_bounds = variables_window_sp->GetBounds();
5138480093f4SDimitry Andric           Rect new_vars_rect;
5139480093f4SDimitry Andric           variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect,
5140480093f4SDimitry Andric                                                    new_regs_rect);
5141480093f4SDimitry Andric           variables_window_sp->SetBounds(new_vars_rect);
5142480093f4SDimitry Andric         } else {
5143af732203SDimitry Andric           // No variables window, grab the bottom part of the source window
5144480093f4SDimitry Andric           Rect new_source_rect;
5145480093f4SDimitry Andric           source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
5146480093f4SDimitry Andric                                                   new_regs_rect);
5147480093f4SDimitry Andric           source_window_sp->SetBounds(new_source_rect);
5148480093f4SDimitry Andric         }
5149480093f4SDimitry Andric         WindowSP new_window_sp =
5150480093f4SDimitry Andric             main_window_sp->CreateSubWindow("Registers", new_regs_rect, false);
5151480093f4SDimitry Andric         new_window_sp->SetDelegate(
5152480093f4SDimitry Andric             WindowDelegateSP(new RegistersWindowDelegate(m_debugger)));
5153480093f4SDimitry Andric       }
5154480093f4SDimitry Andric       touchwin(stdscr);
5155480093f4SDimitry Andric     }
5156480093f4SDimitry Andric       return MenuActionResult::Handled;
5157480093f4SDimitry Andric 
5158480093f4SDimitry Andric     case eMenuID_HelpGUIHelp:
5159480093f4SDimitry Andric       m_app.GetMainWindow()->CreateHelpSubwindow();
5160480093f4SDimitry Andric       return MenuActionResult::Handled;
5161480093f4SDimitry Andric 
5162480093f4SDimitry Andric     default:
5163480093f4SDimitry Andric       break;
5164480093f4SDimitry Andric     }
5165480093f4SDimitry Andric 
5166480093f4SDimitry Andric     return MenuActionResult::NotHandled;
5167480093f4SDimitry Andric   }
5168480093f4SDimitry Andric 
5169480093f4SDimitry Andric protected:
5170480093f4SDimitry Andric   Application &m_app;
5171480093f4SDimitry Andric   Debugger &m_debugger;
5172480093f4SDimitry Andric };
5173480093f4SDimitry Andric 
5174480093f4SDimitry Andric class StatusBarWindowDelegate : public WindowDelegate {
5175480093f4SDimitry Andric public:
StatusBarWindowDelegate(Debugger & debugger)5176480093f4SDimitry Andric   StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) {
5177480093f4SDimitry Andric     FormatEntity::Parse("Thread: ${thread.id%tid}", m_format);
5178480093f4SDimitry Andric   }
5179480093f4SDimitry Andric 
5180480093f4SDimitry Andric   ~StatusBarWindowDelegate() override = default;
5181480093f4SDimitry Andric 
WindowDelegateDraw(Window & window,bool force)5182480093f4SDimitry Andric   bool WindowDelegateDraw(Window &window, bool force) override {
5183480093f4SDimitry Andric     ExecutionContext exe_ctx =
5184480093f4SDimitry Andric         m_debugger.GetCommandInterpreter().GetExecutionContext();
5185480093f4SDimitry Andric     Process *process = exe_ctx.GetProcessPtr();
5186480093f4SDimitry Andric     Thread *thread = exe_ctx.GetThreadPtr();
5187480093f4SDimitry Andric     StackFrame *frame = exe_ctx.GetFramePtr();
5188480093f4SDimitry Andric     window.Erase();
5189af732203SDimitry Andric     window.SetBackground(BlackOnWhite);
5190480093f4SDimitry Andric     window.MoveCursor(0, 0);
5191480093f4SDimitry Andric     if (process) {
5192480093f4SDimitry Andric       const StateType state = process->GetState();
5193480093f4SDimitry Andric       window.Printf("Process: %5" PRIu64 " %10s", process->GetID(),
5194480093f4SDimitry Andric                     StateAsCString(state));
5195480093f4SDimitry Andric 
5196480093f4SDimitry Andric       if (StateIsStoppedState(state, true)) {
5197480093f4SDimitry Andric         StreamString strm;
5198480093f4SDimitry Andric         if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx,
5199480093f4SDimitry Andric                                            nullptr, nullptr, false, false)) {
5200480093f4SDimitry Andric           window.MoveCursor(40, 0);
5201af732203SDimitry Andric           window.PutCStringTruncated(1, strm.GetString().str().c_str());
5202480093f4SDimitry Andric         }
5203480093f4SDimitry Andric 
5204480093f4SDimitry Andric         window.MoveCursor(60, 0);
5205480093f4SDimitry Andric         if (frame)
5206480093f4SDimitry Andric           window.Printf("Frame: %3u  PC = 0x%16.16" PRIx64,
5207480093f4SDimitry Andric                         frame->GetFrameIndex(),
5208480093f4SDimitry Andric                         frame->GetFrameCodeAddress().GetOpcodeLoadAddress(
5209480093f4SDimitry Andric                             exe_ctx.GetTargetPtr()));
5210480093f4SDimitry Andric       } else if (state == eStateExited) {
5211480093f4SDimitry Andric         const char *exit_desc = process->GetExitDescription();
5212480093f4SDimitry Andric         const int exit_status = process->GetExitStatus();
5213480093f4SDimitry Andric         if (exit_desc && exit_desc[0])
5214480093f4SDimitry Andric           window.Printf(" with status = %i (%s)", exit_status, exit_desc);
5215480093f4SDimitry Andric         else
5216480093f4SDimitry Andric           window.Printf(" with status = %i", exit_status);
5217480093f4SDimitry Andric       }
5218480093f4SDimitry Andric     }
5219480093f4SDimitry Andric     return true;
5220480093f4SDimitry Andric   }
5221480093f4SDimitry Andric 
5222480093f4SDimitry Andric protected:
5223480093f4SDimitry Andric   Debugger &m_debugger;
5224480093f4SDimitry Andric   FormatEntity::Entry m_format;
5225480093f4SDimitry Andric };
5226480093f4SDimitry Andric 
5227480093f4SDimitry Andric class SourceFileWindowDelegate : public WindowDelegate {
5228480093f4SDimitry Andric public:
SourceFileWindowDelegate(Debugger & debugger)5229480093f4SDimitry Andric   SourceFileWindowDelegate(Debugger &debugger)
5230480093f4SDimitry Andric       : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(),
5231480093f4SDimitry Andric         m_disassembly_scope(nullptr), m_disassembly_sp(), m_disassembly_range(),
5232*5f7ddb14SDimitry Andric         m_title(), m_tid(LLDB_INVALID_THREAD_ID), m_line_width(4),
5233*5f7ddb14SDimitry Andric         m_selected_line(0), m_pc_line(0), m_stop_id(0), m_frame_idx(UINT32_MAX),
5234*5f7ddb14SDimitry Andric         m_first_visible_line(0), m_first_visible_column(0), m_min_x(0),
5235*5f7ddb14SDimitry Andric         m_min_y(0), m_max_x(0), m_max_y(0) {}
5236480093f4SDimitry Andric 
5237480093f4SDimitry Andric   ~SourceFileWindowDelegate() override = default;
5238480093f4SDimitry Andric 
Update(const SymbolContext & sc)5239480093f4SDimitry Andric   void Update(const SymbolContext &sc) { m_sc = sc; }
5240480093f4SDimitry Andric 
NumVisibleLines() const5241480093f4SDimitry Andric   uint32_t NumVisibleLines() const { return m_max_y - m_min_y; }
5242480093f4SDimitry Andric 
WindowDelegateGetHelpText()5243480093f4SDimitry Andric   const char *WindowDelegateGetHelpText() override {
5244480093f4SDimitry Andric     return "Source/Disassembly window keyboard shortcuts:";
5245480093f4SDimitry Andric   }
5246480093f4SDimitry Andric 
WindowDelegateGetKeyHelp()5247480093f4SDimitry Andric   KeyHelp *WindowDelegateGetKeyHelp() override {
5248480093f4SDimitry Andric     static curses::KeyHelp g_source_view_key_help[] = {
5249480093f4SDimitry Andric         {KEY_RETURN, "Run to selected line with one shot breakpoint"},
5250480093f4SDimitry Andric         {KEY_UP, "Select previous source line"},
5251480093f4SDimitry Andric         {KEY_DOWN, "Select next source line"},
5252af732203SDimitry Andric         {KEY_LEFT, "Scroll to the left"},
5253af732203SDimitry Andric         {KEY_RIGHT, "Scroll to the right"},
5254480093f4SDimitry Andric         {KEY_PPAGE, "Page up"},
5255480093f4SDimitry Andric         {KEY_NPAGE, "Page down"},
5256480093f4SDimitry Andric         {'b', "Set breakpoint on selected source/disassembly line"},
5257480093f4SDimitry Andric         {'c', "Continue process"},
5258480093f4SDimitry Andric         {'D', "Detach with process suspended"},
5259480093f4SDimitry Andric         {'h', "Show help dialog"},
5260480093f4SDimitry Andric         {'n', "Step over (source line)"},
5261480093f4SDimitry Andric         {'N', "Step over (single instruction)"},
5262af732203SDimitry Andric         {'f', "Step out (finish)"},
5263480093f4SDimitry Andric         {'s', "Step in (source line)"},
5264480093f4SDimitry Andric         {'S', "Step in (single instruction)"},
5265af732203SDimitry Andric         {'u', "Frame up"},
5266af732203SDimitry Andric         {'d', "Frame down"},
5267480093f4SDimitry Andric         {',', "Page up"},
5268480093f4SDimitry Andric         {'.', "Page down"},
5269480093f4SDimitry Andric         {'\0', nullptr}};
5270480093f4SDimitry Andric     return g_source_view_key_help;
5271480093f4SDimitry Andric   }
5272480093f4SDimitry Andric 
WindowDelegateDraw(Window & window,bool force)5273480093f4SDimitry Andric   bool WindowDelegateDraw(Window &window, bool force) override {
5274480093f4SDimitry Andric     ExecutionContext exe_ctx =
5275480093f4SDimitry Andric         m_debugger.GetCommandInterpreter().GetExecutionContext();
5276480093f4SDimitry Andric     Process *process = exe_ctx.GetProcessPtr();
5277480093f4SDimitry Andric     Thread *thread = nullptr;
5278480093f4SDimitry Andric 
5279480093f4SDimitry Andric     bool update_location = false;
5280480093f4SDimitry Andric     if (process) {
5281480093f4SDimitry Andric       StateType state = process->GetState();
5282480093f4SDimitry Andric       if (StateIsStoppedState(state, true)) {
5283480093f4SDimitry Andric         // We are stopped, so it is ok to
5284480093f4SDimitry Andric         update_location = true;
5285480093f4SDimitry Andric       }
5286480093f4SDimitry Andric     }
5287480093f4SDimitry Andric 
5288480093f4SDimitry Andric     m_min_x = 1;
5289480093f4SDimitry Andric     m_min_y = 2;
5290480093f4SDimitry Andric     m_max_x = window.GetMaxX() - 1;
5291480093f4SDimitry Andric     m_max_y = window.GetMaxY() - 1;
5292480093f4SDimitry Andric 
5293480093f4SDimitry Andric     const uint32_t num_visible_lines = NumVisibleLines();
5294480093f4SDimitry Andric     StackFrameSP frame_sp;
5295480093f4SDimitry Andric     bool set_selected_line_to_pc = false;
5296480093f4SDimitry Andric 
5297480093f4SDimitry Andric     if (update_location) {
5298480093f4SDimitry Andric       const bool process_alive = process ? process->IsAlive() : false;
5299480093f4SDimitry Andric       bool thread_changed = false;
5300480093f4SDimitry Andric       if (process_alive) {
5301480093f4SDimitry Andric         thread = exe_ctx.GetThreadPtr();
5302480093f4SDimitry Andric         if (thread) {
5303480093f4SDimitry Andric           frame_sp = thread->GetSelectedFrame();
5304480093f4SDimitry Andric           auto tid = thread->GetID();
5305480093f4SDimitry Andric           thread_changed = tid != m_tid;
5306480093f4SDimitry Andric           m_tid = tid;
5307480093f4SDimitry Andric         } else {
5308480093f4SDimitry Andric           if (m_tid != LLDB_INVALID_THREAD_ID) {
5309480093f4SDimitry Andric             thread_changed = true;
5310480093f4SDimitry Andric             m_tid = LLDB_INVALID_THREAD_ID;
5311480093f4SDimitry Andric           }
5312480093f4SDimitry Andric         }
5313480093f4SDimitry Andric       }
5314480093f4SDimitry Andric       const uint32_t stop_id = process ? process->GetStopID() : 0;
5315480093f4SDimitry Andric       const bool stop_id_changed = stop_id != m_stop_id;
5316480093f4SDimitry Andric       bool frame_changed = false;
5317480093f4SDimitry Andric       m_stop_id = stop_id;
5318480093f4SDimitry Andric       m_title.Clear();
5319480093f4SDimitry Andric       if (frame_sp) {
5320480093f4SDimitry Andric         m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything);
5321480093f4SDimitry Andric         if (m_sc.module_sp) {
5322480093f4SDimitry Andric           m_title.Printf(
5323480093f4SDimitry Andric               "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString());
5324480093f4SDimitry Andric           ConstString func_name = m_sc.GetFunctionName();
5325480093f4SDimitry Andric           if (func_name)
5326480093f4SDimitry Andric             m_title.Printf("`%s", func_name.GetCString());
5327480093f4SDimitry Andric         }
5328480093f4SDimitry Andric         const uint32_t frame_idx = frame_sp->GetFrameIndex();
5329480093f4SDimitry Andric         frame_changed = frame_idx != m_frame_idx;
5330480093f4SDimitry Andric         m_frame_idx = frame_idx;
5331480093f4SDimitry Andric       } else {
5332480093f4SDimitry Andric         m_sc.Clear(true);
5333480093f4SDimitry Andric         frame_changed = m_frame_idx != UINT32_MAX;
5334480093f4SDimitry Andric         m_frame_idx = UINT32_MAX;
5335480093f4SDimitry Andric       }
5336480093f4SDimitry Andric 
5337480093f4SDimitry Andric       const bool context_changed =
5338480093f4SDimitry Andric           thread_changed || frame_changed || stop_id_changed;
5339480093f4SDimitry Andric 
5340480093f4SDimitry Andric       if (process_alive) {
5341480093f4SDimitry Andric         if (m_sc.line_entry.IsValid()) {
5342480093f4SDimitry Andric           m_pc_line = m_sc.line_entry.line;
5343480093f4SDimitry Andric           if (m_pc_line != UINT32_MAX)
5344480093f4SDimitry Andric             --m_pc_line; // Convert to zero based line number...
5345480093f4SDimitry Andric           // Update the selected line if the stop ID changed...
5346480093f4SDimitry Andric           if (context_changed)
5347480093f4SDimitry Andric             m_selected_line = m_pc_line;
5348480093f4SDimitry Andric 
5349480093f4SDimitry Andric           if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) {
5350480093f4SDimitry Andric             // Same file, nothing to do, we should either have the lines or not
5351480093f4SDimitry Andric             // (source file missing)
5352480093f4SDimitry Andric             if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) {
5353480093f4SDimitry Andric               if (m_selected_line >= m_first_visible_line + num_visible_lines)
5354480093f4SDimitry Andric                 m_first_visible_line = m_selected_line - 10;
5355480093f4SDimitry Andric             } else {
5356480093f4SDimitry Andric               if (m_selected_line > 10)
5357480093f4SDimitry Andric                 m_first_visible_line = m_selected_line - 10;
5358480093f4SDimitry Andric               else
5359480093f4SDimitry Andric                 m_first_visible_line = 0;
5360480093f4SDimitry Andric             }
5361480093f4SDimitry Andric           } else {
5362480093f4SDimitry Andric             // File changed, set selected line to the line with the PC
5363480093f4SDimitry Andric             m_selected_line = m_pc_line;
5364480093f4SDimitry Andric             m_file_sp =
5365480093f4SDimitry Andric                 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file);
5366480093f4SDimitry Andric             if (m_file_sp) {
5367480093f4SDimitry Andric               const size_t num_lines = m_file_sp->GetNumLines();
5368480093f4SDimitry Andric               m_line_width = 1;
5369480093f4SDimitry Andric               for (size_t n = num_lines; n >= 10; n = n / 10)
5370480093f4SDimitry Andric                 ++m_line_width;
5371480093f4SDimitry Andric 
5372480093f4SDimitry Andric               if (num_lines < num_visible_lines ||
5373480093f4SDimitry Andric                   m_selected_line < num_visible_lines)
5374480093f4SDimitry Andric                 m_first_visible_line = 0;
5375480093f4SDimitry Andric               else
5376480093f4SDimitry Andric                 m_first_visible_line = m_selected_line - 10;
5377480093f4SDimitry Andric             }
5378480093f4SDimitry Andric           }
5379480093f4SDimitry Andric         } else {
5380480093f4SDimitry Andric           m_file_sp.reset();
5381480093f4SDimitry Andric         }
5382480093f4SDimitry Andric 
5383480093f4SDimitry Andric         if (!m_file_sp || m_file_sp->GetNumLines() == 0) {
5384480093f4SDimitry Andric           // Show disassembly
5385480093f4SDimitry Andric           bool prefer_file_cache = false;
5386480093f4SDimitry Andric           if (m_sc.function) {
5387480093f4SDimitry Andric             if (m_disassembly_scope != m_sc.function) {
5388480093f4SDimitry Andric               m_disassembly_scope = m_sc.function;
5389480093f4SDimitry Andric               m_disassembly_sp = m_sc.function->GetInstructions(
5390*5f7ddb14SDimitry Andric                   exe_ctx, nullptr, !prefer_file_cache);
5391480093f4SDimitry Andric               if (m_disassembly_sp) {
5392480093f4SDimitry Andric                 set_selected_line_to_pc = true;
5393480093f4SDimitry Andric                 m_disassembly_range = m_sc.function->GetAddressRange();
5394480093f4SDimitry Andric               } else {
5395480093f4SDimitry Andric                 m_disassembly_range.Clear();
5396480093f4SDimitry Andric               }
5397480093f4SDimitry Andric             } else {
5398480093f4SDimitry Andric               set_selected_line_to_pc = context_changed;
5399480093f4SDimitry Andric             }
5400480093f4SDimitry Andric           } else if (m_sc.symbol) {
5401480093f4SDimitry Andric             if (m_disassembly_scope != m_sc.symbol) {
5402480093f4SDimitry Andric               m_disassembly_scope = m_sc.symbol;
5403480093f4SDimitry Andric               m_disassembly_sp = m_sc.symbol->GetInstructions(
5404480093f4SDimitry Andric                   exe_ctx, nullptr, prefer_file_cache);
5405480093f4SDimitry Andric               if (m_disassembly_sp) {
5406480093f4SDimitry Andric                 set_selected_line_to_pc = true;
5407480093f4SDimitry Andric                 m_disassembly_range.GetBaseAddress() =
5408480093f4SDimitry Andric                     m_sc.symbol->GetAddress();
5409480093f4SDimitry Andric                 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize());
5410480093f4SDimitry Andric               } else {
5411480093f4SDimitry Andric                 m_disassembly_range.Clear();
5412480093f4SDimitry Andric               }
5413480093f4SDimitry Andric             } else {
5414480093f4SDimitry Andric               set_selected_line_to_pc = context_changed;
5415480093f4SDimitry Andric             }
5416480093f4SDimitry Andric           }
5417480093f4SDimitry Andric         }
5418480093f4SDimitry Andric       } else {
5419480093f4SDimitry Andric         m_pc_line = UINT32_MAX;
5420480093f4SDimitry Andric       }
5421480093f4SDimitry Andric     }
5422480093f4SDimitry Andric 
5423480093f4SDimitry Andric     const int window_width = window.GetWidth();
5424480093f4SDimitry Andric     window.Erase();
5425480093f4SDimitry Andric     window.DrawTitleBox("Sources");
5426480093f4SDimitry Andric     if (!m_title.GetString().empty()) {
5427480093f4SDimitry Andric       window.AttributeOn(A_REVERSE);
5428480093f4SDimitry Andric       window.MoveCursor(1, 1);
5429480093f4SDimitry Andric       window.PutChar(' ');
5430af732203SDimitry Andric       window.PutCStringTruncated(1, m_title.GetString().str().c_str());
5431480093f4SDimitry Andric       int x = window.GetCursorX();
5432480093f4SDimitry Andric       if (x < window_width - 1) {
5433480093f4SDimitry Andric         window.Printf("%*s", window_width - x - 1, "");
5434480093f4SDimitry Andric       }
5435480093f4SDimitry Andric       window.AttributeOff(A_REVERSE);
5436480093f4SDimitry Andric     }
5437480093f4SDimitry Andric 
5438480093f4SDimitry Andric     Target *target = exe_ctx.GetTargetPtr();
5439480093f4SDimitry Andric     const size_t num_source_lines = GetNumSourceLines();
5440480093f4SDimitry Andric     if (num_source_lines > 0) {
5441480093f4SDimitry Andric       // Display source
5442480093f4SDimitry Andric       BreakpointLines bp_lines;
5443480093f4SDimitry Andric       if (target) {
5444480093f4SDimitry Andric         BreakpointList &bp_list = target->GetBreakpointList();
5445480093f4SDimitry Andric         const size_t num_bps = bp_list.GetSize();
5446480093f4SDimitry Andric         for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
5447480093f4SDimitry Andric           BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
5448480093f4SDimitry Andric           const size_t num_bps_locs = bp_sp->GetNumLocations();
5449480093f4SDimitry Andric           for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
5450480093f4SDimitry Andric             BreakpointLocationSP bp_loc_sp =
5451480093f4SDimitry Andric                 bp_sp->GetLocationAtIndex(bp_loc_idx);
5452480093f4SDimitry Andric             LineEntry bp_loc_line_entry;
5453480093f4SDimitry Andric             if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
5454480093f4SDimitry Andric                     bp_loc_line_entry)) {
5455480093f4SDimitry Andric               if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) {
5456480093f4SDimitry Andric                 bp_lines.insert(bp_loc_line_entry.line);
5457480093f4SDimitry Andric               }
5458480093f4SDimitry Andric             }
5459480093f4SDimitry Andric           }
5460480093f4SDimitry Andric         }
5461480093f4SDimitry Andric       }
5462480093f4SDimitry Andric 
5463480093f4SDimitry Andric       const attr_t selected_highlight_attr = A_REVERSE;
5464af732203SDimitry Andric       const attr_t pc_highlight_attr = COLOR_PAIR(BlackOnBlue);
5465480093f4SDimitry Andric 
5466480093f4SDimitry Andric       for (size_t i = 0; i < num_visible_lines; ++i) {
5467480093f4SDimitry Andric         const uint32_t curr_line = m_first_visible_line + i;
5468480093f4SDimitry Andric         if (curr_line < num_source_lines) {
5469480093f4SDimitry Andric           const int line_y = m_min_y + i;
5470480093f4SDimitry Andric           window.MoveCursor(1, line_y);
5471480093f4SDimitry Andric           const bool is_pc_line = curr_line == m_pc_line;
5472480093f4SDimitry Andric           const bool line_is_selected = m_selected_line == curr_line;
5473480093f4SDimitry Andric           // Highlight the line as the PC line first, then if the selected line
5474480093f4SDimitry Andric           // isn't the same as the PC line, highlight it differently
5475480093f4SDimitry Andric           attr_t highlight_attr = 0;
5476480093f4SDimitry Andric           attr_t bp_attr = 0;
5477480093f4SDimitry Andric           if (is_pc_line)
5478480093f4SDimitry Andric             highlight_attr = pc_highlight_attr;
5479480093f4SDimitry Andric           else if (line_is_selected)
5480480093f4SDimitry Andric             highlight_attr = selected_highlight_attr;
5481480093f4SDimitry Andric 
5482480093f4SDimitry Andric           if (bp_lines.find(curr_line + 1) != bp_lines.end())
5483af732203SDimitry Andric             bp_attr = COLOR_PAIR(BlackOnWhite);
5484480093f4SDimitry Andric 
5485480093f4SDimitry Andric           if (bp_attr)
5486480093f4SDimitry Andric             window.AttributeOn(bp_attr);
5487480093f4SDimitry Andric 
5488480093f4SDimitry Andric           window.Printf(" %*u ", m_line_width, curr_line + 1);
5489480093f4SDimitry Andric 
5490480093f4SDimitry Andric           if (bp_attr)
5491480093f4SDimitry Andric             window.AttributeOff(bp_attr);
5492480093f4SDimitry Andric 
5493480093f4SDimitry Andric           window.PutChar(ACS_VLINE);
5494480093f4SDimitry Andric           // Mark the line with the PC with a diamond
5495480093f4SDimitry Andric           if (is_pc_line)
5496480093f4SDimitry Andric             window.PutChar(ACS_DIAMOND);
5497480093f4SDimitry Andric           else
5498480093f4SDimitry Andric             window.PutChar(' ');
5499480093f4SDimitry Andric 
5500480093f4SDimitry Andric           if (highlight_attr)
5501480093f4SDimitry Andric             window.AttributeOn(highlight_attr);
5502af732203SDimitry Andric 
5503af732203SDimitry Andric           StreamString lineStream;
5504af732203SDimitry Andric           m_file_sp->DisplaySourceLines(curr_line + 1, {}, 0, 0, &lineStream);
5505af732203SDimitry Andric           StringRef line = lineStream.GetString();
5506af732203SDimitry Andric           if (line.endswith("\n"))
5507af732203SDimitry Andric             line = line.drop_back();
5508af732203SDimitry Andric           bool wasWritten = window.OutputColoredStringTruncated(
5509af732203SDimitry Andric               1, line, m_first_visible_column, line_is_selected);
5510af732203SDimitry Andric           if (line_is_selected && !wasWritten) {
5511af732203SDimitry Andric             // Draw an empty space to show the selected line if empty,
5512af732203SDimitry Andric             // or draw '<' if nothing is visible because of scrolling too much
5513af732203SDimitry Andric             // to the right.
5514af732203SDimitry Andric             window.PutCStringTruncated(
5515af732203SDimitry Andric                 1, line.empty() && m_first_visible_column == 0 ? " " : "<");
5516af732203SDimitry Andric           }
5517480093f4SDimitry Andric 
5518480093f4SDimitry Andric           if (is_pc_line && frame_sp &&
5519480093f4SDimitry Andric               frame_sp->GetConcreteFrameIndex() == 0) {
5520480093f4SDimitry Andric             StopInfoSP stop_info_sp;
5521480093f4SDimitry Andric             if (thread)
5522480093f4SDimitry Andric               stop_info_sp = thread->GetStopInfo();
5523480093f4SDimitry Andric             if (stop_info_sp) {
5524480093f4SDimitry Andric               const char *stop_description = stop_info_sp->GetDescription();
5525480093f4SDimitry Andric               if (stop_description && stop_description[0]) {
5526480093f4SDimitry Andric                 size_t stop_description_len = strlen(stop_description);
5527480093f4SDimitry Andric                 int desc_x = window_width - stop_description_len - 16;
5528af732203SDimitry Andric                 if (desc_x - window.GetCursorX() > 0)
5529480093f4SDimitry Andric                   window.Printf("%*s", desc_x - window.GetCursorX(), "");
5530af732203SDimitry Andric                 window.MoveCursor(window_width - stop_description_len - 16,
5531af732203SDimitry Andric                                   line_y);
5532af732203SDimitry Andric                 const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue);
5533af732203SDimitry Andric                 window.AttributeOn(stop_reason_attr);
5534af732203SDimitry Andric                 window.PrintfTruncated(1, " <<< Thread %u: %s ",
5535af732203SDimitry Andric                                        thread->GetIndexID(), stop_description);
5536af732203SDimitry Andric                 window.AttributeOff(stop_reason_attr);
5537480093f4SDimitry Andric               }
5538480093f4SDimitry Andric             } else {
5539480093f4SDimitry Andric               window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
5540480093f4SDimitry Andric             }
5541480093f4SDimitry Andric           }
5542480093f4SDimitry Andric           if (highlight_attr)
5543480093f4SDimitry Andric             window.AttributeOff(highlight_attr);
5544480093f4SDimitry Andric         } else {
5545480093f4SDimitry Andric           break;
5546480093f4SDimitry Andric         }
5547480093f4SDimitry Andric       }
5548480093f4SDimitry Andric     } else {
5549480093f4SDimitry Andric       size_t num_disassembly_lines = GetNumDisassemblyLines();
5550480093f4SDimitry Andric       if (num_disassembly_lines > 0) {
5551480093f4SDimitry Andric         // Display disassembly
5552480093f4SDimitry Andric         BreakpointAddrs bp_file_addrs;
5553480093f4SDimitry Andric         Target *target = exe_ctx.GetTargetPtr();
5554480093f4SDimitry Andric         if (target) {
5555480093f4SDimitry Andric           BreakpointList &bp_list = target->GetBreakpointList();
5556480093f4SDimitry Andric           const size_t num_bps = bp_list.GetSize();
5557480093f4SDimitry Andric           for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
5558480093f4SDimitry Andric             BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
5559480093f4SDimitry Andric             const size_t num_bps_locs = bp_sp->GetNumLocations();
5560480093f4SDimitry Andric             for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs;
5561480093f4SDimitry Andric                  ++bp_loc_idx) {
5562480093f4SDimitry Andric               BreakpointLocationSP bp_loc_sp =
5563480093f4SDimitry Andric                   bp_sp->GetLocationAtIndex(bp_loc_idx);
5564480093f4SDimitry Andric               LineEntry bp_loc_line_entry;
5565480093f4SDimitry Andric               const lldb::addr_t file_addr =
5566480093f4SDimitry Andric                   bp_loc_sp->GetAddress().GetFileAddress();
5567480093f4SDimitry Andric               if (file_addr != LLDB_INVALID_ADDRESS) {
5568480093f4SDimitry Andric                 if (m_disassembly_range.ContainsFileAddress(file_addr))
5569480093f4SDimitry Andric                   bp_file_addrs.insert(file_addr);
5570480093f4SDimitry Andric               }
5571480093f4SDimitry Andric             }
5572480093f4SDimitry Andric           }
5573480093f4SDimitry Andric         }
5574480093f4SDimitry Andric 
5575480093f4SDimitry Andric         const attr_t selected_highlight_attr = A_REVERSE;
5576af732203SDimitry Andric         const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue);
5577480093f4SDimitry Andric 
5578480093f4SDimitry Andric         StreamString strm;
5579480093f4SDimitry Andric 
5580480093f4SDimitry Andric         InstructionList &insts = m_disassembly_sp->GetInstructionList();
5581480093f4SDimitry Andric         Address pc_address;
5582480093f4SDimitry Andric 
5583480093f4SDimitry Andric         if (frame_sp)
5584480093f4SDimitry Andric           pc_address = frame_sp->GetFrameCodeAddress();
5585480093f4SDimitry Andric         const uint32_t pc_idx =
5586480093f4SDimitry Andric             pc_address.IsValid()
5587480093f4SDimitry Andric                 ? insts.GetIndexOfInstructionAtAddress(pc_address)
5588480093f4SDimitry Andric                 : UINT32_MAX;
5589480093f4SDimitry Andric         if (set_selected_line_to_pc) {
5590480093f4SDimitry Andric           m_selected_line = pc_idx;
5591480093f4SDimitry Andric         }
5592480093f4SDimitry Andric 
5593480093f4SDimitry Andric         const uint32_t non_visible_pc_offset = (num_visible_lines / 5);
5594480093f4SDimitry Andric         if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines)
5595480093f4SDimitry Andric           m_first_visible_line = 0;
5596480093f4SDimitry Andric 
5597480093f4SDimitry Andric         if (pc_idx < num_disassembly_lines) {
5598480093f4SDimitry Andric           if (pc_idx < static_cast<uint32_t>(m_first_visible_line) ||
5599480093f4SDimitry Andric               pc_idx >= m_first_visible_line + num_visible_lines)
5600480093f4SDimitry Andric             m_first_visible_line = pc_idx - non_visible_pc_offset;
5601480093f4SDimitry Andric         }
5602480093f4SDimitry Andric 
5603480093f4SDimitry Andric         for (size_t i = 0; i < num_visible_lines; ++i) {
5604480093f4SDimitry Andric           const uint32_t inst_idx = m_first_visible_line + i;
5605480093f4SDimitry Andric           Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get();
5606480093f4SDimitry Andric           if (!inst)
5607480093f4SDimitry Andric             break;
5608480093f4SDimitry Andric 
5609480093f4SDimitry Andric           const int line_y = m_min_y + i;
5610480093f4SDimitry Andric           window.MoveCursor(1, line_y);
5611480093f4SDimitry Andric           const bool is_pc_line = frame_sp && inst_idx == pc_idx;
5612480093f4SDimitry Andric           const bool line_is_selected = m_selected_line == inst_idx;
5613480093f4SDimitry Andric           // Highlight the line as the PC line first, then if the selected line
5614480093f4SDimitry Andric           // isn't the same as the PC line, highlight it differently
5615480093f4SDimitry Andric           attr_t highlight_attr = 0;
5616480093f4SDimitry Andric           attr_t bp_attr = 0;
5617480093f4SDimitry Andric           if (is_pc_line)
5618480093f4SDimitry Andric             highlight_attr = pc_highlight_attr;
5619480093f4SDimitry Andric           else if (line_is_selected)
5620480093f4SDimitry Andric             highlight_attr = selected_highlight_attr;
5621480093f4SDimitry Andric 
5622480093f4SDimitry Andric           if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) !=
5623480093f4SDimitry Andric               bp_file_addrs.end())
5624af732203SDimitry Andric             bp_attr = COLOR_PAIR(BlackOnWhite);
5625480093f4SDimitry Andric 
5626480093f4SDimitry Andric           if (bp_attr)
5627480093f4SDimitry Andric             window.AttributeOn(bp_attr);
5628480093f4SDimitry Andric 
5629480093f4SDimitry Andric           window.Printf(" 0x%16.16llx ",
5630480093f4SDimitry Andric                         static_cast<unsigned long long>(
5631480093f4SDimitry Andric                             inst->GetAddress().GetLoadAddress(target)));
5632480093f4SDimitry Andric 
5633480093f4SDimitry Andric           if (bp_attr)
5634480093f4SDimitry Andric             window.AttributeOff(bp_attr);
5635480093f4SDimitry Andric 
5636480093f4SDimitry Andric           window.PutChar(ACS_VLINE);
5637480093f4SDimitry Andric           // Mark the line with the PC with a diamond
5638480093f4SDimitry Andric           if (is_pc_line)
5639480093f4SDimitry Andric             window.PutChar(ACS_DIAMOND);
5640480093f4SDimitry Andric           else
5641480093f4SDimitry Andric             window.PutChar(' ');
5642480093f4SDimitry Andric 
5643480093f4SDimitry Andric           if (highlight_attr)
5644480093f4SDimitry Andric             window.AttributeOn(highlight_attr);
5645480093f4SDimitry Andric 
5646480093f4SDimitry Andric           const char *mnemonic = inst->GetMnemonic(&exe_ctx);
5647480093f4SDimitry Andric           const char *operands = inst->GetOperands(&exe_ctx);
5648480093f4SDimitry Andric           const char *comment = inst->GetComment(&exe_ctx);
5649480093f4SDimitry Andric 
5650480093f4SDimitry Andric           if (mnemonic != nullptr && mnemonic[0] == '\0')
5651480093f4SDimitry Andric             mnemonic = nullptr;
5652480093f4SDimitry Andric           if (operands != nullptr && operands[0] == '\0')
5653480093f4SDimitry Andric             operands = nullptr;
5654480093f4SDimitry Andric           if (comment != nullptr && comment[0] == '\0')
5655480093f4SDimitry Andric             comment = nullptr;
5656480093f4SDimitry Andric 
5657480093f4SDimitry Andric           strm.Clear();
5658480093f4SDimitry Andric 
5659480093f4SDimitry Andric           if (mnemonic != nullptr && operands != nullptr && comment != nullptr)
5660480093f4SDimitry Andric             strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment);
5661480093f4SDimitry Andric           else if (mnemonic != nullptr && operands != nullptr)
5662480093f4SDimitry Andric             strm.Printf("%-8s %s", mnemonic, operands);
5663480093f4SDimitry Andric           else if (mnemonic != nullptr)
5664480093f4SDimitry Andric             strm.Printf("%s", mnemonic);
5665480093f4SDimitry Andric 
5666480093f4SDimitry Andric           int right_pad = 1;
5667af732203SDimitry Andric           window.PutCStringTruncated(
5668af732203SDimitry Andric               right_pad,
5669af732203SDimitry Andric               strm.GetString().substr(m_first_visible_column).data());
5670480093f4SDimitry Andric 
5671480093f4SDimitry Andric           if (is_pc_line && frame_sp &&
5672480093f4SDimitry Andric               frame_sp->GetConcreteFrameIndex() == 0) {
5673480093f4SDimitry Andric             StopInfoSP stop_info_sp;
5674480093f4SDimitry Andric             if (thread)
5675480093f4SDimitry Andric               stop_info_sp = thread->GetStopInfo();
5676480093f4SDimitry Andric             if (stop_info_sp) {
5677480093f4SDimitry Andric               const char *stop_description = stop_info_sp->GetDescription();
5678480093f4SDimitry Andric               if (stop_description && stop_description[0]) {
5679480093f4SDimitry Andric                 size_t stop_description_len = strlen(stop_description);
5680480093f4SDimitry Andric                 int desc_x = window_width - stop_description_len - 16;
5681af732203SDimitry Andric                 if (desc_x - window.GetCursorX() > 0)
5682480093f4SDimitry Andric                   window.Printf("%*s", desc_x - window.GetCursorX(), "");
5683af732203SDimitry Andric                 window.MoveCursor(window_width - stop_description_len - 15,
5684af732203SDimitry Andric                                   line_y);
5685af732203SDimitry Andric                 window.PrintfTruncated(1, "<<< Thread %u: %s ",
5686af732203SDimitry Andric                                        thread->GetIndexID(), stop_description);
5687480093f4SDimitry Andric               }
5688480093f4SDimitry Andric             } else {
5689480093f4SDimitry Andric               window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
5690480093f4SDimitry Andric             }
5691480093f4SDimitry Andric           }
5692480093f4SDimitry Andric           if (highlight_attr)
5693480093f4SDimitry Andric             window.AttributeOff(highlight_attr);
5694480093f4SDimitry Andric         }
5695480093f4SDimitry Andric       }
5696480093f4SDimitry Andric     }
5697480093f4SDimitry Andric     return true; // Drawing handled
5698480093f4SDimitry Andric   }
5699480093f4SDimitry Andric 
GetNumLines()5700480093f4SDimitry Andric   size_t GetNumLines() {
5701480093f4SDimitry Andric     size_t num_lines = GetNumSourceLines();
5702480093f4SDimitry Andric     if (num_lines == 0)
5703480093f4SDimitry Andric       num_lines = GetNumDisassemblyLines();
5704480093f4SDimitry Andric     return num_lines;
5705480093f4SDimitry Andric   }
5706480093f4SDimitry Andric 
GetNumSourceLines() const5707480093f4SDimitry Andric   size_t GetNumSourceLines() const {
5708480093f4SDimitry Andric     if (m_file_sp)
5709480093f4SDimitry Andric       return m_file_sp->GetNumLines();
5710480093f4SDimitry Andric     return 0;
5711480093f4SDimitry Andric   }
5712480093f4SDimitry Andric 
GetNumDisassemblyLines() const5713480093f4SDimitry Andric   size_t GetNumDisassemblyLines() const {
5714480093f4SDimitry Andric     if (m_disassembly_sp)
5715480093f4SDimitry Andric       return m_disassembly_sp->GetInstructionList().GetSize();
5716480093f4SDimitry Andric     return 0;
5717480093f4SDimitry Andric   }
5718480093f4SDimitry Andric 
WindowDelegateHandleChar(Window & window,int c)5719480093f4SDimitry Andric   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
5720480093f4SDimitry Andric     const uint32_t num_visible_lines = NumVisibleLines();
5721480093f4SDimitry Andric     const size_t num_lines = GetNumLines();
5722480093f4SDimitry Andric 
5723480093f4SDimitry Andric     switch (c) {
5724480093f4SDimitry Andric     case ',':
5725480093f4SDimitry Andric     case KEY_PPAGE:
5726480093f4SDimitry Andric       // Page up key
5727480093f4SDimitry Andric       if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines)
5728480093f4SDimitry Andric         m_first_visible_line -= num_visible_lines;
5729480093f4SDimitry Andric       else
5730480093f4SDimitry Andric         m_first_visible_line = 0;
5731480093f4SDimitry Andric       m_selected_line = m_first_visible_line;
5732480093f4SDimitry Andric       return eKeyHandled;
5733480093f4SDimitry Andric 
5734480093f4SDimitry Andric     case '.':
5735480093f4SDimitry Andric     case KEY_NPAGE:
5736480093f4SDimitry Andric       // Page down key
5737480093f4SDimitry Andric       {
5738480093f4SDimitry Andric         if (m_first_visible_line + num_visible_lines < num_lines)
5739480093f4SDimitry Andric           m_first_visible_line += num_visible_lines;
5740480093f4SDimitry Andric         else if (num_lines < num_visible_lines)
5741480093f4SDimitry Andric           m_first_visible_line = 0;
5742480093f4SDimitry Andric         else
5743480093f4SDimitry Andric           m_first_visible_line = num_lines - num_visible_lines;
5744480093f4SDimitry Andric         m_selected_line = m_first_visible_line;
5745480093f4SDimitry Andric       }
5746480093f4SDimitry Andric       return eKeyHandled;
5747480093f4SDimitry Andric 
5748480093f4SDimitry Andric     case KEY_UP:
5749480093f4SDimitry Andric       if (m_selected_line > 0) {
5750480093f4SDimitry Andric         m_selected_line--;
5751480093f4SDimitry Andric         if (static_cast<size_t>(m_first_visible_line) > m_selected_line)
5752480093f4SDimitry Andric           m_first_visible_line = m_selected_line;
5753480093f4SDimitry Andric       }
5754480093f4SDimitry Andric       return eKeyHandled;
5755480093f4SDimitry Andric 
5756480093f4SDimitry Andric     case KEY_DOWN:
5757480093f4SDimitry Andric       if (m_selected_line + 1 < num_lines) {
5758480093f4SDimitry Andric         m_selected_line++;
5759480093f4SDimitry Andric         if (m_first_visible_line + num_visible_lines < m_selected_line)
5760480093f4SDimitry Andric           m_first_visible_line++;
5761480093f4SDimitry Andric       }
5762480093f4SDimitry Andric       return eKeyHandled;
5763480093f4SDimitry Andric 
5764af732203SDimitry Andric     case KEY_LEFT:
5765af732203SDimitry Andric       if (m_first_visible_column > 0)
5766af732203SDimitry Andric         --m_first_visible_column;
5767af732203SDimitry Andric       return eKeyHandled;
5768af732203SDimitry Andric 
5769af732203SDimitry Andric     case KEY_RIGHT:
5770af732203SDimitry Andric       ++m_first_visible_column;
5771af732203SDimitry Andric       return eKeyHandled;
5772af732203SDimitry Andric 
5773480093f4SDimitry Andric     case '\r':
5774480093f4SDimitry Andric     case '\n':
5775480093f4SDimitry Andric     case KEY_ENTER:
5776480093f4SDimitry Andric       // Set a breakpoint and run to the line using a one shot breakpoint
5777480093f4SDimitry Andric       if (GetNumSourceLines() > 0) {
5778480093f4SDimitry Andric         ExecutionContext exe_ctx =
5779480093f4SDimitry Andric             m_debugger.GetCommandInterpreter().GetExecutionContext();
5780480093f4SDimitry Andric         if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) {
5781480093f4SDimitry Andric           BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
5782480093f4SDimitry Andric               nullptr, // Don't limit the breakpoint to certain modules
5783480093f4SDimitry Andric               m_file_sp->GetFileSpec(), // Source file
5784480093f4SDimitry Andric               m_selected_line +
5785480093f4SDimitry Andric                   1, // Source line number (m_selected_line is zero based)
5786480093f4SDimitry Andric               0,     // Unspecified column.
5787480093f4SDimitry Andric               0,     // No offset
5788480093f4SDimitry Andric               eLazyBoolCalculate,  // Check inlines using global setting
5789480093f4SDimitry Andric               eLazyBoolCalculate,  // Skip prologue using global setting,
5790480093f4SDimitry Andric               false,               // internal
5791480093f4SDimitry Andric               false,               // request_hardware
5792480093f4SDimitry Andric               eLazyBoolCalculate); // move_to_nearest_code
5793480093f4SDimitry Andric           // Make breakpoint one shot
5794*5f7ddb14SDimitry Andric           bp_sp->GetOptions().SetOneShot(true);
5795480093f4SDimitry Andric           exe_ctx.GetProcessRef().Resume();
5796480093f4SDimitry Andric         }
5797480093f4SDimitry Andric       } else if (m_selected_line < GetNumDisassemblyLines()) {
5798480093f4SDimitry Andric         const Instruction *inst = m_disassembly_sp->GetInstructionList()
5799480093f4SDimitry Andric                                       .GetInstructionAtIndex(m_selected_line)
5800480093f4SDimitry Andric                                       .get();
5801480093f4SDimitry Andric         ExecutionContext exe_ctx =
5802480093f4SDimitry Andric             m_debugger.GetCommandInterpreter().GetExecutionContext();
5803480093f4SDimitry Andric         if (exe_ctx.HasTargetScope()) {
5804480093f4SDimitry Andric           Address addr = inst->GetAddress();
5805480093f4SDimitry Andric           BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
5806480093f4SDimitry Andric               addr,   // lldb_private::Address
5807480093f4SDimitry Andric               false,  // internal
5808480093f4SDimitry Andric               false); // request_hardware
5809480093f4SDimitry Andric           // Make breakpoint one shot
5810*5f7ddb14SDimitry Andric           bp_sp->GetOptions().SetOneShot(true);
5811480093f4SDimitry Andric           exe_ctx.GetProcessRef().Resume();
5812480093f4SDimitry Andric         }
5813480093f4SDimitry Andric       }
5814480093f4SDimitry Andric       return eKeyHandled;
5815480093f4SDimitry Andric 
5816480093f4SDimitry Andric     case 'b': // 'b' == toggle breakpoint on currently selected line
5817af732203SDimitry Andric       ToggleBreakpointOnSelectedLine();
5818480093f4SDimitry Andric       return eKeyHandled;
5819480093f4SDimitry Andric 
5820480093f4SDimitry Andric     case 'D': // 'D' == detach and keep stopped
5821480093f4SDimitry Andric     {
5822480093f4SDimitry Andric       ExecutionContext exe_ctx =
5823480093f4SDimitry Andric           m_debugger.GetCommandInterpreter().GetExecutionContext();
5824480093f4SDimitry Andric       if (exe_ctx.HasProcessScope())
5825af732203SDimitry Andric         exe_ctx.GetProcessRef().Detach(true);
5826480093f4SDimitry Andric     }
5827480093f4SDimitry Andric       return eKeyHandled;
5828480093f4SDimitry Andric 
5829480093f4SDimitry Andric     case 'c':
5830480093f4SDimitry Andric       // 'c' == continue
5831480093f4SDimitry Andric       {
5832480093f4SDimitry Andric         ExecutionContext exe_ctx =
5833480093f4SDimitry Andric             m_debugger.GetCommandInterpreter().GetExecutionContext();
5834480093f4SDimitry Andric         if (exe_ctx.HasProcessScope())
5835480093f4SDimitry Andric           exe_ctx.GetProcessRef().Resume();
5836480093f4SDimitry Andric       }
5837480093f4SDimitry Andric       return eKeyHandled;
5838480093f4SDimitry Andric 
5839af732203SDimitry Andric     case 'f':
5840af732203SDimitry Andric       // 'f' == step out (finish)
5841480093f4SDimitry Andric       {
5842480093f4SDimitry Andric         ExecutionContext exe_ctx =
5843480093f4SDimitry Andric             m_debugger.GetCommandInterpreter().GetExecutionContext();
5844480093f4SDimitry Andric         if (exe_ctx.HasThreadScope() &&
5845480093f4SDimitry Andric             StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
5846480093f4SDimitry Andric           exe_ctx.GetThreadRef().StepOut();
5847480093f4SDimitry Andric         }
5848480093f4SDimitry Andric       }
5849480093f4SDimitry Andric       return eKeyHandled;
5850480093f4SDimitry Andric 
5851480093f4SDimitry Andric     case 'n': // 'n' == step over
5852480093f4SDimitry Andric     case 'N': // 'N' == step over instruction
5853480093f4SDimitry Andric     {
5854480093f4SDimitry Andric       ExecutionContext exe_ctx =
5855480093f4SDimitry Andric           m_debugger.GetCommandInterpreter().GetExecutionContext();
5856480093f4SDimitry Andric       if (exe_ctx.HasThreadScope() &&
5857480093f4SDimitry Andric           StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
5858480093f4SDimitry Andric         bool source_step = (c == 'n');
5859480093f4SDimitry Andric         exe_ctx.GetThreadRef().StepOver(source_step);
5860480093f4SDimitry Andric       }
5861480093f4SDimitry Andric     }
5862480093f4SDimitry Andric       return eKeyHandled;
5863480093f4SDimitry Andric 
5864480093f4SDimitry Andric     case 's': // 's' == step into
5865480093f4SDimitry Andric     case 'S': // 'S' == step into instruction
5866480093f4SDimitry Andric     {
5867480093f4SDimitry Andric       ExecutionContext exe_ctx =
5868480093f4SDimitry Andric           m_debugger.GetCommandInterpreter().GetExecutionContext();
5869480093f4SDimitry Andric       if (exe_ctx.HasThreadScope() &&
5870480093f4SDimitry Andric           StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
5871480093f4SDimitry Andric         bool source_step = (c == 's');
5872480093f4SDimitry Andric         exe_ctx.GetThreadRef().StepIn(source_step);
5873480093f4SDimitry Andric       }
5874480093f4SDimitry Andric     }
5875480093f4SDimitry Andric       return eKeyHandled;
5876480093f4SDimitry Andric 
5877af732203SDimitry Andric     case 'u': // 'u' == frame up
5878af732203SDimitry Andric     case 'd': // 'd' == frame down
5879af732203SDimitry Andric     {
5880af732203SDimitry Andric       ExecutionContext exe_ctx =
5881af732203SDimitry Andric           m_debugger.GetCommandInterpreter().GetExecutionContext();
5882af732203SDimitry Andric       if (exe_ctx.HasThreadScope()) {
5883af732203SDimitry Andric         Thread *thread = exe_ctx.GetThreadPtr();
5884af732203SDimitry Andric         uint32_t frame_idx = thread->GetSelectedFrameIndex();
5885af732203SDimitry Andric         if (frame_idx == UINT32_MAX)
5886af732203SDimitry Andric           frame_idx = 0;
5887af732203SDimitry Andric         if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount())
5888af732203SDimitry Andric           ++frame_idx;
5889af732203SDimitry Andric         else if (c == 'd' && frame_idx > 0)
5890af732203SDimitry Andric           --frame_idx;
5891af732203SDimitry Andric         if (thread->SetSelectedFrameByIndex(frame_idx, true))
5892af732203SDimitry Andric           exe_ctx.SetFrameSP(thread->GetSelectedFrame());
5893af732203SDimitry Andric       }
5894af732203SDimitry Andric     }
5895af732203SDimitry Andric       return eKeyHandled;
5896af732203SDimitry Andric 
5897480093f4SDimitry Andric     case 'h':
5898480093f4SDimitry Andric       window.CreateHelpSubwindow();
5899480093f4SDimitry Andric       return eKeyHandled;
5900480093f4SDimitry Andric 
5901480093f4SDimitry Andric     default:
5902480093f4SDimitry Andric       break;
5903480093f4SDimitry Andric     }
5904480093f4SDimitry Andric     return eKeyNotHandled;
5905480093f4SDimitry Andric   }
5906480093f4SDimitry Andric 
ToggleBreakpointOnSelectedLine()5907af732203SDimitry Andric   void ToggleBreakpointOnSelectedLine() {
5908af732203SDimitry Andric     ExecutionContext exe_ctx =
5909af732203SDimitry Andric         m_debugger.GetCommandInterpreter().GetExecutionContext();
5910af732203SDimitry Andric     if (!exe_ctx.HasTargetScope())
5911af732203SDimitry Andric       return;
5912af732203SDimitry Andric     if (GetNumSourceLines() > 0) {
5913af732203SDimitry Andric       // Source file breakpoint.
5914af732203SDimitry Andric       BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
5915af732203SDimitry Andric       const size_t num_bps = bp_list.GetSize();
5916af732203SDimitry Andric       for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
5917af732203SDimitry Andric         BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
5918af732203SDimitry Andric         const size_t num_bps_locs = bp_sp->GetNumLocations();
5919af732203SDimitry Andric         for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
5920af732203SDimitry Andric           BreakpointLocationSP bp_loc_sp =
5921af732203SDimitry Andric               bp_sp->GetLocationAtIndex(bp_loc_idx);
5922af732203SDimitry Andric           LineEntry bp_loc_line_entry;
5923af732203SDimitry Andric           if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
5924af732203SDimitry Andric                   bp_loc_line_entry)) {
5925af732203SDimitry Andric             if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file &&
5926af732203SDimitry Andric                 m_selected_line + 1 == bp_loc_line_entry.line) {
5927af732203SDimitry Andric               bool removed =
5928af732203SDimitry Andric                   exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
5929af732203SDimitry Andric               assert(removed);
5930af732203SDimitry Andric               UNUSED_IF_ASSERT_DISABLED(removed);
5931af732203SDimitry Andric               return; // Existing breakpoint removed.
5932af732203SDimitry Andric             }
5933af732203SDimitry Andric           }
5934af732203SDimitry Andric         }
5935af732203SDimitry Andric       }
5936af732203SDimitry Andric       // No breakpoint found on the location, add it.
5937af732203SDimitry Andric       BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
5938af732203SDimitry Andric           nullptr, // Don't limit the breakpoint to certain modules
5939af732203SDimitry Andric           m_file_sp->GetFileSpec(), // Source file
5940af732203SDimitry Andric           m_selected_line +
5941af732203SDimitry Andric               1, // Source line number (m_selected_line is zero based)
5942af732203SDimitry Andric           0,     // No column specified.
5943af732203SDimitry Andric           0,     // No offset
5944af732203SDimitry Andric           eLazyBoolCalculate,  // Check inlines using global setting
5945af732203SDimitry Andric           eLazyBoolCalculate,  // Skip prologue using global setting,
5946af732203SDimitry Andric           false,               // internal
5947af732203SDimitry Andric           false,               // request_hardware
5948af732203SDimitry Andric           eLazyBoolCalculate); // move_to_nearest_code
5949af732203SDimitry Andric     } else {
5950af732203SDimitry Andric       // Disassembly breakpoint.
5951af732203SDimitry Andric       assert(GetNumDisassemblyLines() > 0);
5952af732203SDimitry Andric       assert(m_selected_line < GetNumDisassemblyLines());
5953af732203SDimitry Andric       const Instruction *inst = m_disassembly_sp->GetInstructionList()
5954af732203SDimitry Andric                                     .GetInstructionAtIndex(m_selected_line)
5955af732203SDimitry Andric                                     .get();
5956af732203SDimitry Andric       Address addr = inst->GetAddress();
5957af732203SDimitry Andric       // Try to find it.
5958af732203SDimitry Andric       BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
5959af732203SDimitry Andric       const size_t num_bps = bp_list.GetSize();
5960af732203SDimitry Andric       for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
5961af732203SDimitry Andric         BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
5962af732203SDimitry Andric         const size_t num_bps_locs = bp_sp->GetNumLocations();
5963af732203SDimitry Andric         for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
5964af732203SDimitry Andric           BreakpointLocationSP bp_loc_sp =
5965af732203SDimitry Andric               bp_sp->GetLocationAtIndex(bp_loc_idx);
5966af732203SDimitry Andric           LineEntry bp_loc_line_entry;
5967af732203SDimitry Andric           const lldb::addr_t file_addr =
5968af732203SDimitry Andric               bp_loc_sp->GetAddress().GetFileAddress();
5969af732203SDimitry Andric           if (file_addr == addr.GetFileAddress()) {
5970af732203SDimitry Andric             bool removed =
5971af732203SDimitry Andric                 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
5972af732203SDimitry Andric             assert(removed);
5973af732203SDimitry Andric             UNUSED_IF_ASSERT_DISABLED(removed);
5974af732203SDimitry Andric             return; // Existing breakpoint removed.
5975af732203SDimitry Andric           }
5976af732203SDimitry Andric         }
5977af732203SDimitry Andric       }
5978af732203SDimitry Andric       // No breakpoint found on the address, add it.
5979af732203SDimitry Andric       BreakpointSP bp_sp =
5980af732203SDimitry Andric           exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address
5981af732203SDimitry Andric                                                   false,  // internal
5982af732203SDimitry Andric                                                   false); // request_hardware
5983af732203SDimitry Andric     }
5984af732203SDimitry Andric   }
5985af732203SDimitry Andric 
5986480093f4SDimitry Andric protected:
5987480093f4SDimitry Andric   typedef std::set<uint32_t> BreakpointLines;
5988480093f4SDimitry Andric   typedef std::set<lldb::addr_t> BreakpointAddrs;
5989480093f4SDimitry Andric 
5990480093f4SDimitry Andric   Debugger &m_debugger;
5991480093f4SDimitry Andric   SymbolContext m_sc;
5992480093f4SDimitry Andric   SourceManager::FileSP m_file_sp;
5993480093f4SDimitry Andric   SymbolContextScope *m_disassembly_scope;
5994480093f4SDimitry Andric   lldb::DisassemblerSP m_disassembly_sp;
5995480093f4SDimitry Andric   AddressRange m_disassembly_range;
5996480093f4SDimitry Andric   StreamString m_title;
5997480093f4SDimitry Andric   lldb::user_id_t m_tid;
5998480093f4SDimitry Andric   int m_line_width;
5999480093f4SDimitry Andric   uint32_t m_selected_line; // The selected line
6000480093f4SDimitry Andric   uint32_t m_pc_line;       // The line with the PC
6001480093f4SDimitry Andric   uint32_t m_stop_id;
6002480093f4SDimitry Andric   uint32_t m_frame_idx;
6003480093f4SDimitry Andric   int m_first_visible_line;
6004af732203SDimitry Andric   int m_first_visible_column;
6005480093f4SDimitry Andric   int m_min_x;
6006480093f4SDimitry Andric   int m_min_y;
6007480093f4SDimitry Andric   int m_max_x;
6008480093f4SDimitry Andric   int m_max_y;
6009480093f4SDimitry Andric };
6010480093f4SDimitry Andric 
6011480093f4SDimitry Andric DisplayOptions ValueObjectListDelegate::g_options = {true};
6012480093f4SDimitry Andric 
IOHandlerCursesGUI(Debugger & debugger)6013480093f4SDimitry Andric IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger)
6014480093f4SDimitry Andric     : IOHandler(debugger, IOHandler::Type::Curses) {}
6015480093f4SDimitry Andric 
Activate()6016480093f4SDimitry Andric void IOHandlerCursesGUI::Activate() {
6017480093f4SDimitry Andric   IOHandler::Activate();
6018480093f4SDimitry Andric   if (!m_app_ap) {
60195ffd83dbSDimitry Andric     m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE());
6020480093f4SDimitry Andric 
6021480093f4SDimitry Andric     // This is both a window and a menu delegate
6022480093f4SDimitry Andric     std::shared_ptr<ApplicationDelegate> app_delegate_sp(
6023480093f4SDimitry Andric         new ApplicationDelegate(*m_app_ap, m_debugger));
6024480093f4SDimitry Andric 
6025480093f4SDimitry Andric     MenuDelegateSP app_menu_delegate_sp =
6026480093f4SDimitry Andric         std::static_pointer_cast<MenuDelegate>(app_delegate_sp);
6027480093f4SDimitry Andric     MenuSP lldb_menu_sp(
6028480093f4SDimitry Andric         new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB));
6029480093f4SDimitry Andric     MenuSP exit_menuitem_sp(
6030480093f4SDimitry Andric         new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit));
6031480093f4SDimitry Andric     exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit);
6032480093f4SDimitry Andric     lldb_menu_sp->AddSubmenu(MenuSP(new Menu(
6033480093f4SDimitry Andric         "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout)));
6034480093f4SDimitry Andric     lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
6035480093f4SDimitry Andric     lldb_menu_sp->AddSubmenu(exit_menuitem_sp);
6036480093f4SDimitry Andric 
6037480093f4SDimitry Andric     MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2),
6038480093f4SDimitry Andric                                    ApplicationDelegate::eMenuID_Target));
6039480093f4SDimitry Andric     target_menu_sp->AddSubmenu(MenuSP(new Menu(
6040480093f4SDimitry Andric         "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate)));
6041480093f4SDimitry Andric     target_menu_sp->AddSubmenu(MenuSP(new Menu(
6042480093f4SDimitry Andric         "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete)));
6043480093f4SDimitry Andric 
6044480093f4SDimitry Andric     MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3),
6045480093f4SDimitry Andric                                     ApplicationDelegate::eMenuID_Process));
6046480093f4SDimitry Andric     process_menu_sp->AddSubmenu(MenuSP(new Menu(
6047480093f4SDimitry Andric         "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach)));
6048af732203SDimitry Andric     process_menu_sp->AddSubmenu(
6049af732203SDimitry Andric         MenuSP(new Menu("Detach and resume", nullptr, 'd',
6050af732203SDimitry Andric                         ApplicationDelegate::eMenuID_ProcessDetachResume)));
6051af732203SDimitry Andric     process_menu_sp->AddSubmenu(
6052af732203SDimitry Andric         MenuSP(new Menu("Detach suspended", nullptr, 's',
6053af732203SDimitry Andric                         ApplicationDelegate::eMenuID_ProcessDetachSuspended)));
6054480093f4SDimitry Andric     process_menu_sp->AddSubmenu(MenuSP(new Menu(
6055480093f4SDimitry Andric         "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch)));
6056480093f4SDimitry Andric     process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
6057480093f4SDimitry Andric     process_menu_sp->AddSubmenu(
6058480093f4SDimitry Andric         MenuSP(new Menu("Continue", nullptr, 'c',
6059480093f4SDimitry Andric                         ApplicationDelegate::eMenuID_ProcessContinue)));
6060480093f4SDimitry Andric     process_menu_sp->AddSubmenu(MenuSP(new Menu(
6061480093f4SDimitry Andric         "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt)));
6062480093f4SDimitry Andric     process_menu_sp->AddSubmenu(MenuSP(new Menu(
6063480093f4SDimitry Andric         "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill)));
6064480093f4SDimitry Andric 
6065480093f4SDimitry Andric     MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4),
6066480093f4SDimitry Andric                                    ApplicationDelegate::eMenuID_Thread));
6067480093f4SDimitry Andric     thread_menu_sp->AddSubmenu(MenuSP(new Menu(
6068480093f4SDimitry Andric         "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn)));
6069480093f4SDimitry Andric     thread_menu_sp->AddSubmenu(
6070480093f4SDimitry Andric         MenuSP(new Menu("Step Over", nullptr, 'v',
6071480093f4SDimitry Andric                         ApplicationDelegate::eMenuID_ThreadStepOver)));
6072480093f4SDimitry Andric     thread_menu_sp->AddSubmenu(MenuSP(new Menu(
6073480093f4SDimitry Andric         "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut)));
6074480093f4SDimitry Andric 
6075480093f4SDimitry Andric     MenuSP view_menu_sp(
6076480093f4SDimitry Andric         new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View));
6077480093f4SDimitry Andric     view_menu_sp->AddSubmenu(
6078480093f4SDimitry Andric         MenuSP(new Menu("Backtrace", nullptr, 'b',
6079480093f4SDimitry Andric                         ApplicationDelegate::eMenuID_ViewBacktrace)));
6080480093f4SDimitry Andric     view_menu_sp->AddSubmenu(
6081480093f4SDimitry Andric         MenuSP(new Menu("Registers", nullptr, 'r',
6082480093f4SDimitry Andric                         ApplicationDelegate::eMenuID_ViewRegisters)));
6083480093f4SDimitry Andric     view_menu_sp->AddSubmenu(MenuSP(new Menu(
6084480093f4SDimitry Andric         "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource)));
6085480093f4SDimitry Andric     view_menu_sp->AddSubmenu(
6086480093f4SDimitry Andric         MenuSP(new Menu("Variables", nullptr, 'v',
6087480093f4SDimitry Andric                         ApplicationDelegate::eMenuID_ViewVariables)));
6088480093f4SDimitry Andric 
6089480093f4SDimitry Andric     MenuSP help_menu_sp(
6090480093f4SDimitry Andric         new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help));
6091480093f4SDimitry Andric     help_menu_sp->AddSubmenu(MenuSP(new Menu(
6092480093f4SDimitry Andric         "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp)));
6093480093f4SDimitry Andric 
6094480093f4SDimitry Andric     m_app_ap->Initialize();
6095480093f4SDimitry Andric     WindowSP &main_window_sp = m_app_ap->GetMainWindow();
6096480093f4SDimitry Andric 
6097480093f4SDimitry Andric     MenuSP menubar_sp(new Menu(Menu::Type::Bar));
6098480093f4SDimitry Andric     menubar_sp->AddSubmenu(lldb_menu_sp);
6099480093f4SDimitry Andric     menubar_sp->AddSubmenu(target_menu_sp);
6100480093f4SDimitry Andric     menubar_sp->AddSubmenu(process_menu_sp);
6101480093f4SDimitry Andric     menubar_sp->AddSubmenu(thread_menu_sp);
6102480093f4SDimitry Andric     menubar_sp->AddSubmenu(view_menu_sp);
6103480093f4SDimitry Andric     menubar_sp->AddSubmenu(help_menu_sp);
6104480093f4SDimitry Andric     menubar_sp->SetDelegate(app_menu_delegate_sp);
6105480093f4SDimitry Andric 
6106480093f4SDimitry Andric     Rect content_bounds = main_window_sp->GetFrame();
6107480093f4SDimitry Andric     Rect menubar_bounds = content_bounds.MakeMenuBar();
6108480093f4SDimitry Andric     Rect status_bounds = content_bounds.MakeStatusBar();
6109480093f4SDimitry Andric     Rect source_bounds;
6110480093f4SDimitry Andric     Rect variables_bounds;
6111480093f4SDimitry Andric     Rect threads_bounds;
6112480093f4SDimitry Andric     Rect source_variables_bounds;
6113480093f4SDimitry Andric     content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
6114480093f4SDimitry Andric                                            threads_bounds);
6115480093f4SDimitry Andric     source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds,
6116480093f4SDimitry Andric                                                       variables_bounds);
6117480093f4SDimitry Andric 
6118480093f4SDimitry Andric     WindowSP menubar_window_sp =
6119480093f4SDimitry Andric         main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false);
6120480093f4SDimitry Andric     // Let the menubar get keys if the active window doesn't handle the keys
6121480093f4SDimitry Andric     // that are typed so it can respond to menubar key presses.
6122480093f4SDimitry Andric     menubar_window_sp->SetCanBeActive(
6123480093f4SDimitry Andric         false); // Don't let the menubar become the active window
6124480093f4SDimitry Andric     menubar_window_sp->SetDelegate(menubar_sp);
6125480093f4SDimitry Andric 
6126480093f4SDimitry Andric     WindowSP source_window_sp(
6127480093f4SDimitry Andric         main_window_sp->CreateSubWindow("Source", source_bounds, true));
6128480093f4SDimitry Andric     WindowSP variables_window_sp(
6129480093f4SDimitry Andric         main_window_sp->CreateSubWindow("Variables", variables_bounds, false));
6130480093f4SDimitry Andric     WindowSP threads_window_sp(
6131480093f4SDimitry Andric         main_window_sp->CreateSubWindow("Threads", threads_bounds, false));
6132480093f4SDimitry Andric     WindowSP status_window_sp(
6133480093f4SDimitry Andric         main_window_sp->CreateSubWindow("Status", status_bounds, false));
6134480093f4SDimitry Andric     status_window_sp->SetCanBeActive(
6135480093f4SDimitry Andric         false); // Don't let the status bar become the active window
6136480093f4SDimitry Andric     main_window_sp->SetDelegate(
6137480093f4SDimitry Andric         std::static_pointer_cast<WindowDelegate>(app_delegate_sp));
6138480093f4SDimitry Andric     source_window_sp->SetDelegate(
6139480093f4SDimitry Andric         WindowDelegateSP(new SourceFileWindowDelegate(m_debugger)));
6140480093f4SDimitry Andric     variables_window_sp->SetDelegate(
6141480093f4SDimitry Andric         WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
6142480093f4SDimitry Andric     TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger));
6143480093f4SDimitry Andric     threads_window_sp->SetDelegate(WindowDelegateSP(
6144480093f4SDimitry Andric         new TreeWindowDelegate(m_debugger, thread_delegate_sp)));
6145480093f4SDimitry Andric     status_window_sp->SetDelegate(
6146480093f4SDimitry Andric         WindowDelegateSP(new StatusBarWindowDelegate(m_debugger)));
6147480093f4SDimitry Andric 
6148480093f4SDimitry Andric     // Show the main help window once the first time the curses GUI is launched
6149480093f4SDimitry Andric     static bool g_showed_help = false;
6150480093f4SDimitry Andric     if (!g_showed_help) {
6151480093f4SDimitry Andric       g_showed_help = true;
6152480093f4SDimitry Andric       main_window_sp->CreateHelpSubwindow();
6153480093f4SDimitry Andric     }
6154480093f4SDimitry Andric 
6155af732203SDimitry Andric     // All colors with black background.
6156af732203SDimitry Andric     init_pair(1, COLOR_BLACK, COLOR_BLACK);
6157af732203SDimitry Andric     init_pair(2, COLOR_RED, COLOR_BLACK);
6158af732203SDimitry Andric     init_pair(3, COLOR_GREEN, COLOR_BLACK);
6159af732203SDimitry Andric     init_pair(4, COLOR_YELLOW, COLOR_BLACK);
6160af732203SDimitry Andric     init_pair(5, COLOR_BLUE, COLOR_BLACK);
6161af732203SDimitry Andric     init_pair(6, COLOR_MAGENTA, COLOR_BLACK);
6162af732203SDimitry Andric     init_pair(7, COLOR_CYAN, COLOR_BLACK);
6163af732203SDimitry Andric     init_pair(8, COLOR_WHITE, COLOR_BLACK);
6164af732203SDimitry Andric     // All colors with blue background.
6165af732203SDimitry Andric     init_pair(9, COLOR_BLACK, COLOR_BLUE);
6166af732203SDimitry Andric     init_pair(10, COLOR_RED, COLOR_BLUE);
6167af732203SDimitry Andric     init_pair(11, COLOR_GREEN, COLOR_BLUE);
6168af732203SDimitry Andric     init_pair(12, COLOR_YELLOW, COLOR_BLUE);
6169af732203SDimitry Andric     init_pair(13, COLOR_BLUE, COLOR_BLUE);
6170af732203SDimitry Andric     init_pair(14, COLOR_MAGENTA, COLOR_BLUE);
6171af732203SDimitry Andric     init_pair(15, COLOR_CYAN, COLOR_BLUE);
6172af732203SDimitry Andric     init_pair(16, COLOR_WHITE, COLOR_BLUE);
6173af732203SDimitry Andric     // These must match the order in the color indexes enum.
6174af732203SDimitry Andric     init_pair(17, COLOR_BLACK, COLOR_WHITE);
6175af732203SDimitry Andric     init_pair(18, COLOR_MAGENTA, COLOR_WHITE);
6176af732203SDimitry Andric     static_assert(LastColorPairIndex == 18, "Color indexes do not match.");
6177*5f7ddb14SDimitry Andric 
6178*5f7ddb14SDimitry Andric     define_key("\033[Z", KEY_SHIFT_TAB);
6179480093f4SDimitry Andric   }
6180480093f4SDimitry Andric }
6181480093f4SDimitry Andric 
Deactivate()6182480093f4SDimitry Andric void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); }
6183480093f4SDimitry Andric 
Run()6184480093f4SDimitry Andric void IOHandlerCursesGUI::Run() {
6185480093f4SDimitry Andric   m_app_ap->Run(m_debugger);
6186480093f4SDimitry Andric   SetIsDone(true);
6187480093f4SDimitry Andric }
6188480093f4SDimitry Andric 
6189480093f4SDimitry Andric IOHandlerCursesGUI::~IOHandlerCursesGUI() = default;
6190480093f4SDimitry Andric 
Cancel()6191480093f4SDimitry Andric void IOHandlerCursesGUI::Cancel() {}
6192480093f4SDimitry Andric 
Interrupt()6193480093f4SDimitry Andric bool IOHandlerCursesGUI::Interrupt() { return false; }
6194480093f4SDimitry Andric 
GotEOF()6195480093f4SDimitry Andric void IOHandlerCursesGUI::GotEOF() {}
6196480093f4SDimitry Andric 
TerminalSizeChanged()6197af732203SDimitry Andric void IOHandlerCursesGUI::TerminalSizeChanged() {
6198af732203SDimitry Andric   m_app_ap->TerminalSizeChanged();
6199af732203SDimitry Andric }
6200af732203SDimitry Andric 
6201480093f4SDimitry Andric #endif // LLDB_ENABLE_CURSES
6202