1 //===-- IOHandlerCursesGUI.cpp --------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #include "lldb/Core/IOHandlerCursesGUI.h"
10 #include "lldb/Host/Config.h"
11
12 #if LLDB_ENABLE_CURSES
13 #if CURSES_HAVE_NCURSES_CURSES_H
14 #include <ncurses/curses.h>
15 #include <ncurses/panel.h>
16 #else
17 #include <curses.h>
18 #include <panel.h>
19 #endif
20 #endif
21
22 #if defined(__APPLE__)
23 #include <deque>
24 #endif
25 #include <string>
26
27 #include "lldb/Core/Debugger.h"
28 #include "lldb/Core/StreamFile.h"
29 #include "lldb/Core/ValueObjectUpdater.h"
30 #include "lldb/Host/File.h"
31 #include "lldb/Utility/Predicate.h"
32 #include "lldb/Utility/Status.h"
33 #include "lldb/Utility/StreamString.h"
34 #include "lldb/Utility/StringList.h"
35 #include "lldb/lldb-forward.h"
36
37 #include "lldb/Interpreter/CommandCompletions.h"
38 #include "lldb/Interpreter/CommandInterpreter.h"
39 #include "lldb/Interpreter/OptionGroupPlatform.h"
40
41 #if LLDB_ENABLE_CURSES
42 #include "lldb/Breakpoint/BreakpointLocation.h"
43 #include "lldb/Core/Module.h"
44 #include "lldb/Core/PluginManager.h"
45 #include "lldb/Core/ValueObject.h"
46 #include "lldb/Core/ValueObjectRegister.h"
47 #include "lldb/Symbol/Block.h"
48 #include "lldb/Symbol/CompileUnit.h"
49 #include "lldb/Symbol/Function.h"
50 #include "lldb/Symbol/Symbol.h"
51 #include "lldb/Symbol/VariableList.h"
52 #include "lldb/Target/Process.h"
53 #include "lldb/Target/RegisterContext.h"
54 #include "lldb/Target/StackFrame.h"
55 #include "lldb/Target/StopInfo.h"
56 #include "lldb/Target/Target.h"
57 #include "lldb/Target/Thread.h"
58 #include "lldb/Utility/State.h"
59 #endif
60
61 #include "llvm/ADT/StringRef.h"
62
63 #ifdef _WIN32
64 #include "lldb/Host/windows/windows.h"
65 #endif
66
67 #include <memory>
68 #include <mutex>
69
70 #include <cassert>
71 #include <cctype>
72 #include <cerrno>
73 #include <cstdint>
74 #include <cstdio>
75 #include <cstring>
76 #include <functional>
77 #include <type_traits>
78
79 using namespace lldb;
80 using namespace lldb_private;
81 using llvm::None;
82 using llvm::Optional;
83 using llvm::StringRef;
84
85 // we may want curses to be disabled for some builds for instance, windows
86 #if LLDB_ENABLE_CURSES
87
88 #define KEY_CTRL_A 1
89 #define KEY_CTRL_E 5
90 #define KEY_CTRL_K 11
91 #define KEY_RETURN 10
92 #define KEY_ESCAPE 27
93 #define KEY_DELETE 127
94
95 #define KEY_SHIFT_TAB (KEY_MAX + 1)
96 #define KEY_ALT_ENTER (KEY_MAX + 2)
97
98 namespace curses {
99 class Menu;
100 class MenuDelegate;
101 class Window;
102 class WindowDelegate;
103 typedef std::shared_ptr<Menu> MenuSP;
104 typedef std::shared_ptr<MenuDelegate> MenuDelegateSP;
105 typedef std::shared_ptr<Window> WindowSP;
106 typedef std::shared_ptr<WindowDelegate> WindowDelegateSP;
107 typedef std::vector<MenuSP> Menus;
108 typedef std::vector<WindowSP> Windows;
109 typedef std::vector<WindowDelegateSP> WindowDelegates;
110
111 #if 0
112 type summary add -s "x=${var.x}, y=${var.y}" curses::Point
113 type summary add -s "w=${var.width}, h=${var.height}" curses::Size
114 type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect
115 #endif
116
117 struct Point {
118 int x;
119 int y;
120
Pointcurses::Point121 Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}
122
Clearcurses::Point123 void Clear() {
124 x = 0;
125 y = 0;
126 }
127
operator +=curses::Point128 Point &operator+=(const Point &rhs) {
129 x += rhs.x;
130 y += rhs.y;
131 return *this;
132 }
133
Dumpcurses::Point134 void Dump() { printf("(x=%i, y=%i)\n", x, y); }
135 };
136
operator ==(const Point & lhs,const Point & rhs)137 bool operator==(const Point &lhs, const Point &rhs) {
138 return lhs.x == rhs.x && lhs.y == rhs.y;
139 }
140
operator !=(const Point & lhs,const Point & rhs)141 bool operator!=(const Point &lhs, const Point &rhs) {
142 return lhs.x != rhs.x || lhs.y != rhs.y;
143 }
144
145 struct Size {
146 int width;
147 int height;
Sizecurses::Size148 Size(int w = 0, int h = 0) : width(w), height(h) {}
149
Clearcurses::Size150 void Clear() {
151 width = 0;
152 height = 0;
153 }
154
Dumpcurses::Size155 void Dump() { printf("(w=%i, h=%i)\n", width, height); }
156 };
157
operator ==(const Size & lhs,const Size & rhs)158 bool operator==(const Size &lhs, const Size &rhs) {
159 return lhs.width == rhs.width && lhs.height == rhs.height;
160 }
161
operator !=(const Size & lhs,const Size & rhs)162 bool operator!=(const Size &lhs, const Size &rhs) {
163 return lhs.width != rhs.width || lhs.height != rhs.height;
164 }
165
166 struct Rect {
167 Point origin;
168 Size size;
169
Rectcurses::Rect170 Rect() : origin(), size() {}
171
Rectcurses::Rect172 Rect(const Point &p, const Size &s) : origin(p), size(s) {}
173
Clearcurses::Rect174 void Clear() {
175 origin.Clear();
176 size.Clear();
177 }
178
Dumpcurses::Rect179 void Dump() {
180 printf("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width,
181 size.height);
182 }
183
Insetcurses::Rect184 void Inset(int w, int h) {
185 if (size.width > w * 2)
186 size.width -= w * 2;
187 origin.x += w;
188
189 if (size.height > h * 2)
190 size.height -= h * 2;
191 origin.y += h;
192 }
193
194 // Return a status bar rectangle which is the last line of this rectangle.
195 // This rectangle will be modified to not include the status bar area.
MakeStatusBarcurses::Rect196 Rect MakeStatusBar() {
197 Rect status_bar;
198 if (size.height > 1) {
199 status_bar.origin.x = origin.x;
200 status_bar.origin.y = size.height;
201 status_bar.size.width = size.width;
202 status_bar.size.height = 1;
203 --size.height;
204 }
205 return status_bar;
206 }
207
208 // Return a menubar rectangle which is the first line of this rectangle. This
209 // rectangle will be modified to not include the menubar area.
MakeMenuBarcurses::Rect210 Rect MakeMenuBar() {
211 Rect menubar;
212 if (size.height > 1) {
213 menubar.origin.x = origin.x;
214 menubar.origin.y = origin.y;
215 menubar.size.width = size.width;
216 menubar.size.height = 1;
217 ++origin.y;
218 --size.height;
219 }
220 return menubar;
221 }
222
HorizontalSplitPercentagecurses::Rect223 void HorizontalSplitPercentage(float top_percentage, Rect &top,
224 Rect &bottom) const {
225 float top_height = top_percentage * size.height;
226 HorizontalSplit(top_height, top, bottom);
227 }
228
HorizontalSplitcurses::Rect229 void HorizontalSplit(int top_height, Rect &top, Rect &bottom) const {
230 top = *this;
231 if (top_height < size.height) {
232 top.size.height = top_height;
233 bottom.origin.x = origin.x;
234 bottom.origin.y = origin.y + top.size.height;
235 bottom.size.width = size.width;
236 bottom.size.height = size.height - top.size.height;
237 } else {
238 bottom.Clear();
239 }
240 }
241
VerticalSplitPercentagecurses::Rect242 void VerticalSplitPercentage(float left_percentage, Rect &left,
243 Rect &right) const {
244 float left_width = left_percentage * size.width;
245 VerticalSplit(left_width, left, right);
246 }
247
VerticalSplitcurses::Rect248 void VerticalSplit(int left_width, Rect &left, Rect &right) const {
249 left = *this;
250 if (left_width < size.width) {
251 left.size.width = left_width;
252 right.origin.x = origin.x + left.size.width;
253 right.origin.y = origin.y;
254 right.size.width = size.width - left.size.width;
255 right.size.height = size.height;
256 } else {
257 right.Clear();
258 }
259 }
260 };
261
operator ==(const Rect & lhs,const Rect & rhs)262 bool operator==(const Rect &lhs, const Rect &rhs) {
263 return lhs.origin == rhs.origin && lhs.size == rhs.size;
264 }
265
operator !=(const Rect & lhs,const Rect & rhs)266 bool operator!=(const Rect &lhs, const Rect &rhs) {
267 return lhs.origin != rhs.origin || lhs.size != rhs.size;
268 }
269
270 enum HandleCharResult {
271 eKeyNotHandled = 0,
272 eKeyHandled = 1,
273 eQuitApplication = 2
274 };
275
276 enum class MenuActionResult {
277 Handled,
278 NotHandled,
279 Quit // Exit all menus and quit
280 };
281
282 struct KeyHelp {
283 int ch;
284 const char *description;
285 };
286
287 // COLOR_PAIR index names
288 enum {
289 // First 16 colors are 8 black background and 8 blue background colors,
290 // needed by OutputColoredStringTruncated().
291 BlackOnBlack = 1,
292 RedOnBlack,
293 GreenOnBlack,
294 YellowOnBlack,
295 BlueOnBlack,
296 MagentaOnBlack,
297 CyanOnBlack,
298 WhiteOnBlack,
299 BlackOnBlue,
300 RedOnBlue,
301 GreenOnBlue,
302 YellowOnBlue,
303 BlueOnBlue,
304 MagentaOnBlue,
305 CyanOnBlue,
306 WhiteOnBlue,
307 // Other colors, as needed.
308 BlackOnWhite,
309 MagentaOnWhite,
310 LastColorPairIndex = MagentaOnWhite
311 };
312
313 class WindowDelegate {
314 public:
315 virtual ~WindowDelegate() = default;
316
WindowDelegateDraw(Window & window,bool force)317 virtual bool WindowDelegateDraw(Window &window, bool force) {
318 return false; // Drawing not handled
319 }
320
WindowDelegateHandleChar(Window & window,int key)321 virtual HandleCharResult WindowDelegateHandleChar(Window &window, int key) {
322 return eKeyNotHandled;
323 }
324
WindowDelegateGetHelpText()325 virtual const char *WindowDelegateGetHelpText() { return nullptr; }
326
WindowDelegateGetKeyHelp()327 virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; }
328 };
329
330 class HelpDialogDelegate : public WindowDelegate {
331 public:
332 HelpDialogDelegate(const char *text, KeyHelp *key_help_array);
333
334 ~HelpDialogDelegate() override;
335
336 bool WindowDelegateDraw(Window &window, bool force) override;
337
338 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
339
GetNumLines() const340 size_t GetNumLines() const { return m_text.GetSize(); }
341
GetMaxLineLength() const342 size_t GetMaxLineLength() const { return m_text.GetMaxStringLength(); }
343
344 protected:
345 StringList m_text;
346 int m_first_visible_line;
347 };
348
349 // A surface is an abstraction for something than can be drawn on. The surface
350 // have a width, a height, a cursor position, and a multitude of drawing
351 // operations. This type should be sub-classed to get an actually useful ncurses
352 // object, such as a Window or a Pad.
353 class Surface {
354 public:
355 enum class Type { Window, Pad };
356
Surface(Surface::Type type)357 Surface(Surface::Type type) : m_type(type), m_window(nullptr) {}
358
get()359 WINDOW *get() { return m_window; }
360
operator WINDOW*()361 operator WINDOW *() { return m_window; }
362
SubSurface(Rect bounds)363 Surface SubSurface(Rect bounds) {
364 Surface subSurface(m_type);
365 if (m_type == Type::Pad)
366 subSurface.m_window =
367 ::subpad(m_window, bounds.size.height, bounds.size.width,
368 bounds.origin.y, bounds.origin.x);
369 else
370 subSurface.m_window =
371 ::derwin(m_window, bounds.size.height, bounds.size.width,
372 bounds.origin.y, bounds.origin.x);
373 return subSurface;
374 }
375
376 // Copy a region of the surface to another surface.
CopyToSurface(Surface & target,Point source_origin,Point target_origin,Size size)377 void CopyToSurface(Surface &target, Point source_origin, Point target_origin,
378 Size size) {
379 ::copywin(m_window, target.get(), source_origin.y, source_origin.x,
380 target_origin.y, target_origin.x,
381 target_origin.y + size.height - 1,
382 target_origin.x + size.width - 1, false);
383 }
384
GetCursorX() const385 int GetCursorX() const { return getcurx(m_window); }
GetCursorY() const386 int GetCursorY() const { return getcury(m_window); }
MoveCursor(int x,int y)387 void MoveCursor(int x, int y) { ::wmove(m_window, y, x); }
388
AttributeOn(attr_t attr)389 void AttributeOn(attr_t attr) { ::wattron(m_window, attr); }
AttributeOff(attr_t attr)390 void AttributeOff(attr_t attr) { ::wattroff(m_window, attr); }
391
GetMaxX() const392 int GetMaxX() const { return getmaxx(m_window); }
GetMaxY() const393 int GetMaxY() const { return getmaxy(m_window); }
GetWidth() const394 int GetWidth() const { return GetMaxX(); }
GetHeight() const395 int GetHeight() const { return GetMaxY(); }
GetSize() const396 Size GetSize() const { return Size(GetWidth(), GetHeight()); }
397 // Get a zero origin rectangle width the surface size.
GetFrame() const398 Rect GetFrame() const { return Rect(Point(), GetSize()); }
399
Clear()400 void Clear() { ::wclear(m_window); }
Erase()401 void Erase() { ::werase(m_window); }
402
SetBackground(int color_pair_idx)403 void SetBackground(int color_pair_idx) {
404 ::wbkgd(m_window, COLOR_PAIR(color_pair_idx));
405 }
406
PutChar(int ch)407 void PutChar(int ch) { ::waddch(m_window, ch); }
PutCString(const char * s,int len=-1)408 void PutCString(const char *s, int len = -1) { ::waddnstr(m_window, s, len); }
409
PutCStringTruncated(int right_pad,const char * s,int len=-1)410 void PutCStringTruncated(int right_pad, const char *s, int len = -1) {
411 int bytes_left = GetWidth() - GetCursorX();
412 if (bytes_left > right_pad) {
413 bytes_left -= right_pad;
414 ::waddnstr(m_window, s, len < 0 ? bytes_left : std::min(bytes_left, len));
415 }
416 }
417
Printf(const char * format,...)418 void Printf(const char *format, ...) __attribute__((format(printf, 2, 3))) {
419 va_list args;
420 va_start(args, format);
421 vw_printw(m_window, format, args);
422 va_end(args);
423 }
424
PrintfTruncated(int right_pad,const char * format,...)425 void PrintfTruncated(int right_pad, const char *format, ...)
426 __attribute__((format(printf, 3, 4))) {
427 va_list args;
428 va_start(args, format);
429 StreamString strm;
430 strm.PrintfVarArg(format, args);
431 va_end(args);
432 PutCStringTruncated(right_pad, strm.GetData());
433 }
434
VerticalLine(int n,chtype v_char=ACS_VLINE)435 void VerticalLine(int n, chtype v_char = ACS_VLINE) {
436 ::wvline(m_window, v_char, n);
437 }
HorizontalLine(int n,chtype h_char=ACS_HLINE)438 void HorizontalLine(int n, chtype h_char = ACS_HLINE) {
439 ::whline(m_window, h_char, n);
440 }
Box(chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)441 void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
442 ::box(m_window, v_char, h_char);
443 }
444
TitledBox(const char * title,chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)445 void TitledBox(const char *title, chtype v_char = ACS_VLINE,
446 chtype h_char = ACS_HLINE) {
447 Box(v_char, h_char);
448 int title_offset = 2;
449 MoveCursor(title_offset, 0);
450 PutChar('[');
451 PutCString(title, GetWidth() - title_offset);
452 PutChar(']');
453 }
454
Box(const Rect & bounds,chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)455 void Box(const Rect &bounds, chtype v_char = ACS_VLINE,
456 chtype h_char = ACS_HLINE) {
457 MoveCursor(bounds.origin.x, bounds.origin.y);
458 VerticalLine(bounds.size.height);
459 HorizontalLine(bounds.size.width);
460 PutChar(ACS_ULCORNER);
461
462 MoveCursor(bounds.origin.x + bounds.size.width - 1, bounds.origin.y);
463 VerticalLine(bounds.size.height);
464 PutChar(ACS_URCORNER);
465
466 MoveCursor(bounds.origin.x, bounds.origin.y + bounds.size.height - 1);
467 HorizontalLine(bounds.size.width);
468 PutChar(ACS_LLCORNER);
469
470 MoveCursor(bounds.origin.x + bounds.size.width - 1,
471 bounds.origin.y + bounds.size.height - 1);
472 PutChar(ACS_LRCORNER);
473 }
474
TitledBox(const Rect & bounds,const char * title,chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)475 void TitledBox(const Rect &bounds, const char *title,
476 chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
477 Box(bounds, v_char, h_char);
478 int title_offset = 2;
479 MoveCursor(bounds.origin.x + title_offset, bounds.origin.y);
480 PutChar('[');
481 PutCString(title, bounds.size.width - title_offset);
482 PutChar(']');
483 }
484
485 // Curses doesn't allow direct output of color escape sequences, but that's
486 // how we get source lines from the Highligher class. Read the line and
487 // convert color escape sequences to curses color attributes. Use
488 // first_skip_count to skip leading visible characters. Returns false if all
489 // visible characters were skipped due to first_skip_count.
OutputColoredStringTruncated(int right_pad,StringRef string,size_t skip_first_count,bool use_blue_background)490 bool OutputColoredStringTruncated(int right_pad, StringRef string,
491 size_t skip_first_count,
492 bool use_blue_background) {
493 attr_t saved_attr;
494 short saved_pair;
495 bool result = false;
496 wattr_get(m_window, &saved_attr, &saved_pair, nullptr);
497 if (use_blue_background)
498 ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
499 while (!string.empty()) {
500 size_t esc_pos = string.find('\x1b');
501 if (esc_pos == StringRef::npos) {
502 string = string.substr(skip_first_count);
503 if (!string.empty()) {
504 PutCStringTruncated(right_pad, string.data(), string.size());
505 result = true;
506 }
507 break;
508 }
509 if (esc_pos > 0) {
510 if (skip_first_count > 0) {
511 int skip = std::min(esc_pos, skip_first_count);
512 string = string.substr(skip);
513 skip_first_count -= skip;
514 esc_pos -= skip;
515 }
516 if (esc_pos > 0) {
517 PutCStringTruncated(right_pad, string.data(), esc_pos);
518 result = true;
519 string = string.drop_front(esc_pos);
520 }
521 }
522 bool consumed = string.consume_front("\x1b");
523 assert(consumed);
524 UNUSED_IF_ASSERT_DISABLED(consumed);
525 // This is written to match our Highlighter classes, which seem to
526 // generate only foreground color escape sequences. If necessary, this
527 // will need to be extended.
528 if (!string.consume_front("[")) {
529 llvm::errs() << "Missing '[' in color escape sequence.\n";
530 continue;
531 }
532 // Only 8 basic foreground colors and reset, our Highlighter doesn't use
533 // anything else.
534 int value;
535 if (!!string.consumeInteger(10, value) || // Returns false on success.
536 !(value == 0 || (value >= 30 && value <= 37))) {
537 llvm::errs() << "No valid color code in color escape sequence.\n";
538 continue;
539 }
540 if (!string.consume_front("m")) {
541 llvm::errs() << "Missing 'm' in color escape sequence.\n";
542 continue;
543 }
544 if (value == 0) { // Reset.
545 wattr_set(m_window, saved_attr, saved_pair, nullptr);
546 if (use_blue_background)
547 ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
548 } else {
549 // Mapped directly to first 16 color pairs (black/blue background).
550 ::wattron(m_window,
551 COLOR_PAIR(value - 30 + 1 + (use_blue_background ? 8 : 0)));
552 }
553 }
554 wattr_set(m_window, saved_attr, saved_pair, nullptr);
555 return result;
556 }
557
558 protected:
559 Type m_type;
560 WINDOW *m_window;
561 };
562
563 class Pad : public Surface {
564 public:
Pad(Size size)565 Pad(Size size) : Surface(Surface::Type::Pad) {
566 m_window = ::newpad(size.height, size.width);
567 }
568
~Pad()569 ~Pad() { ::delwin(m_window); }
570 };
571
572 class Window : public Surface {
573 public:
Window(const char * name)574 Window(const char *name)
575 : Surface(Surface::Type::Window), m_name(name), m_panel(nullptr),
576 m_parent(nullptr), m_subwindows(), m_delegate_sp(),
577 m_curr_active_window_idx(UINT32_MAX),
578 m_prev_active_window_idx(UINT32_MAX), m_delete(false),
579 m_needs_update(true), m_can_activate(true), m_is_subwin(false) {}
580
Window(const char * name,WINDOW * w,bool del=true)581 Window(const char *name, WINDOW *w, bool del = true)
582 : Surface(Surface::Type::Window), m_name(name), m_panel(nullptr),
583 m_parent(nullptr), m_subwindows(), m_delegate_sp(),
584 m_curr_active_window_idx(UINT32_MAX),
585 m_prev_active_window_idx(UINT32_MAX), m_delete(del),
586 m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
587 if (w)
588 Reset(w);
589 }
590
Window(const char * name,const Rect & bounds)591 Window(const char *name, const Rect &bounds)
592 : Surface(Surface::Type::Window), m_name(name), m_parent(nullptr),
593 m_subwindows(), m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX),
594 m_prev_active_window_idx(UINT32_MAX), m_delete(true),
595 m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
596 Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y,
597 bounds.origin.y));
598 }
599
~Window()600 virtual ~Window() {
601 RemoveSubWindows();
602 Reset();
603 }
604
Reset(WINDOW * w=nullptr,bool del=true)605 void Reset(WINDOW *w = nullptr, bool del = true) {
606 if (m_window == w)
607 return;
608
609 if (m_panel) {
610 ::del_panel(m_panel);
611 m_panel = nullptr;
612 }
613 if (m_window && m_delete) {
614 ::delwin(m_window);
615 m_window = nullptr;
616 m_delete = false;
617 }
618 if (w) {
619 m_window = w;
620 m_panel = ::new_panel(m_window);
621 m_delete = del;
622 }
623 }
624
625 // Get the rectangle in our parent window
GetBounds() const626 Rect GetBounds() const { return Rect(GetParentOrigin(), GetSize()); }
627
GetCenteredRect(int width,int height)628 Rect GetCenteredRect(int width, int height) {
629 Size size = GetSize();
630 width = std::min(size.width, width);
631 height = std::min(size.height, height);
632 int x = (size.width - width) / 2;
633 int y = (size.height - height) / 2;
634 return Rect(Point(x, y), Size(width, height));
635 }
636
GetChar()637 int GetChar() { return ::wgetch(m_window); }
GetParentOrigin() const638 Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); }
GetParentX() const639 int GetParentX() const { return getparx(m_window); }
GetParentY() const640 int GetParentY() const { return getpary(m_window); }
MoveWindow(int x,int y)641 void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); }
Resize(int w,int h)642 void Resize(int w, int h) { ::wresize(m_window, h, w); }
Resize(const Size & size)643 void Resize(const Size &size) {
644 ::wresize(m_window, size.height, size.width);
645 }
MoveWindow(const Point & origin)646 void MoveWindow(const Point &origin) {
647 const bool moving_window = origin != GetParentOrigin();
648 if (m_is_subwin && moving_window) {
649 // Can't move subwindows, must delete and re-create
650 Size size = GetSize();
651 Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y,
652 origin.x),
653 true);
654 } else {
655 ::mvwin(m_window, origin.y, origin.x);
656 }
657 }
658
SetBounds(const Rect & bounds)659 void SetBounds(const Rect &bounds) {
660 const bool moving_window = bounds.origin != GetParentOrigin();
661 if (m_is_subwin && moving_window) {
662 // Can't move subwindows, must delete and re-create
663 Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width,
664 bounds.origin.y, bounds.origin.x),
665 true);
666 } else {
667 if (moving_window)
668 MoveWindow(bounds.origin);
669 Resize(bounds.size);
670 }
671 }
672
Touch()673 void Touch() {
674 ::touchwin(m_window);
675 if (m_parent)
676 m_parent->Touch();
677 }
678
CreateSubWindow(const char * name,const Rect & bounds,bool make_active)679 WindowSP CreateSubWindow(const char *name, const Rect &bounds,
680 bool make_active) {
681 auto get_window = [this, &bounds]() {
682 return m_window
683 ? ::subwin(m_window, bounds.size.height, bounds.size.width,
684 bounds.origin.y, bounds.origin.x)
685 : ::newwin(bounds.size.height, bounds.size.width,
686 bounds.origin.y, bounds.origin.x);
687 };
688 WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true);
689 subwindow_sp->m_is_subwin = subwindow_sp.operator bool();
690 subwindow_sp->m_parent = this;
691 if (make_active) {
692 m_prev_active_window_idx = m_curr_active_window_idx;
693 m_curr_active_window_idx = m_subwindows.size();
694 }
695 m_subwindows.push_back(subwindow_sp);
696 ::top_panel(subwindow_sp->m_panel);
697 m_needs_update = true;
698 return subwindow_sp;
699 }
700
RemoveSubWindow(Window * window)701 bool RemoveSubWindow(Window *window) {
702 Windows::iterator pos, end = m_subwindows.end();
703 size_t i = 0;
704 for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
705 if ((*pos).get() == window) {
706 if (m_prev_active_window_idx == i)
707 m_prev_active_window_idx = UINT32_MAX;
708 else if (m_prev_active_window_idx != UINT32_MAX &&
709 m_prev_active_window_idx > i)
710 --m_prev_active_window_idx;
711
712 if (m_curr_active_window_idx == i)
713 m_curr_active_window_idx = UINT32_MAX;
714 else if (m_curr_active_window_idx != UINT32_MAX &&
715 m_curr_active_window_idx > i)
716 --m_curr_active_window_idx;
717 window->Erase();
718 m_subwindows.erase(pos);
719 m_needs_update = true;
720 if (m_parent)
721 m_parent->Touch();
722 else
723 ::touchwin(stdscr);
724 return true;
725 }
726 }
727 return false;
728 }
729
FindSubWindow(const char * name)730 WindowSP FindSubWindow(const char *name) {
731 Windows::iterator pos, end = m_subwindows.end();
732 size_t i = 0;
733 for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
734 if ((*pos)->m_name == name)
735 return *pos;
736 }
737 return WindowSP();
738 }
739
RemoveSubWindows()740 void RemoveSubWindows() {
741 m_curr_active_window_idx = UINT32_MAX;
742 m_prev_active_window_idx = UINT32_MAX;
743 for (Windows::iterator pos = m_subwindows.begin();
744 pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) {
745 (*pos)->Erase();
746 }
747 if (m_parent)
748 m_parent->Touch();
749 else
750 ::touchwin(stdscr);
751 }
752
753 // Window drawing utilities
DrawTitleBox(const char * title,const char * bottom_message=nullptr)754 void DrawTitleBox(const char *title, const char *bottom_message = nullptr) {
755 attr_t attr = 0;
756 if (IsActive())
757 attr = A_BOLD | COLOR_PAIR(BlackOnWhite);
758 else
759 attr = 0;
760 if (attr)
761 AttributeOn(attr);
762
763 Box();
764 MoveCursor(3, 0);
765
766 if (title && title[0]) {
767 PutChar('<');
768 PutCString(title);
769 PutChar('>');
770 }
771
772 if (bottom_message && bottom_message[0]) {
773 int bottom_message_length = strlen(bottom_message);
774 int x = GetWidth() - 3 - (bottom_message_length + 2);
775
776 if (x > 0) {
777 MoveCursor(x, GetHeight() - 1);
778 PutChar('[');
779 PutCString(bottom_message);
780 PutChar(']');
781 } else {
782 MoveCursor(1, GetHeight() - 1);
783 PutChar('[');
784 PutCStringTruncated(1, bottom_message);
785 }
786 }
787 if (attr)
788 AttributeOff(attr);
789 }
790
Draw(bool force)791 virtual void Draw(bool force) {
792 if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force))
793 return;
794
795 for (auto &subwindow_sp : m_subwindows)
796 subwindow_sp->Draw(force);
797 }
798
CreateHelpSubwindow()799 bool CreateHelpSubwindow() {
800 if (m_delegate_sp) {
801 const char *text = m_delegate_sp->WindowDelegateGetHelpText();
802 KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp();
803 if ((text && text[0]) || key_help) {
804 std::unique_ptr<HelpDialogDelegate> help_delegate_up(
805 new HelpDialogDelegate(text, key_help));
806 const size_t num_lines = help_delegate_up->GetNumLines();
807 const size_t max_length = help_delegate_up->GetMaxLineLength();
808 Rect bounds = GetBounds();
809 bounds.Inset(1, 1);
810 if (max_length + 4 < static_cast<size_t>(bounds.size.width)) {
811 bounds.origin.x += (bounds.size.width - max_length + 4) / 2;
812 bounds.size.width = max_length + 4;
813 } else {
814 if (bounds.size.width > 100) {
815 const int inset_w = bounds.size.width / 4;
816 bounds.origin.x += inset_w;
817 bounds.size.width -= 2 * inset_w;
818 }
819 }
820
821 if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) {
822 bounds.origin.y += (bounds.size.height - num_lines + 2) / 2;
823 bounds.size.height = num_lines + 2;
824 } else {
825 if (bounds.size.height > 100) {
826 const int inset_h = bounds.size.height / 4;
827 bounds.origin.y += inset_h;
828 bounds.size.height -= 2 * inset_h;
829 }
830 }
831 WindowSP help_window_sp;
832 Window *parent_window = GetParent();
833 if (parent_window)
834 help_window_sp = parent_window->CreateSubWindow("Help", bounds, true);
835 else
836 help_window_sp = CreateSubWindow("Help", bounds, true);
837 help_window_sp->SetDelegate(
838 WindowDelegateSP(help_delegate_up.release()));
839 return true;
840 }
841 }
842 return false;
843 }
844
HandleChar(int key)845 virtual HandleCharResult HandleChar(int key) {
846 // Always check the active window first
847 HandleCharResult result = eKeyNotHandled;
848 WindowSP active_window_sp = GetActiveWindow();
849 if (active_window_sp) {
850 result = active_window_sp->HandleChar(key);
851 if (result != eKeyNotHandled)
852 return result;
853 }
854
855 if (m_delegate_sp) {
856 result = m_delegate_sp->WindowDelegateHandleChar(*this, key);
857 if (result != eKeyNotHandled)
858 return result;
859 }
860
861 // Then check for any windows that want any keys that weren't handled. This
862 // is typically only for a menubar. Make a copy of the subwindows in case
863 // any HandleChar() functions muck with the subwindows. If we don't do
864 // this, we can crash when iterating over the subwindows.
865 Windows subwindows(m_subwindows);
866 for (auto subwindow_sp : subwindows) {
867 if (!subwindow_sp->m_can_activate) {
868 HandleCharResult result = subwindow_sp->HandleChar(key);
869 if (result != eKeyNotHandled)
870 return result;
871 }
872 }
873
874 return eKeyNotHandled;
875 }
876
GetActiveWindow()877 WindowSP GetActiveWindow() {
878 if (!m_subwindows.empty()) {
879 if (m_curr_active_window_idx >= m_subwindows.size()) {
880 if (m_prev_active_window_idx < m_subwindows.size()) {
881 m_curr_active_window_idx = m_prev_active_window_idx;
882 m_prev_active_window_idx = UINT32_MAX;
883 } else if (IsActive()) {
884 m_prev_active_window_idx = UINT32_MAX;
885 m_curr_active_window_idx = UINT32_MAX;
886
887 // Find first window that wants to be active if this window is active
888 const size_t num_subwindows = m_subwindows.size();
889 for (size_t i = 0; i < num_subwindows; ++i) {
890 if (m_subwindows[i]->GetCanBeActive()) {
891 m_curr_active_window_idx = i;
892 break;
893 }
894 }
895 }
896 }
897
898 if (m_curr_active_window_idx < m_subwindows.size())
899 return m_subwindows[m_curr_active_window_idx];
900 }
901 return WindowSP();
902 }
903
GetCanBeActive() const904 bool GetCanBeActive() const { return m_can_activate; }
905
SetCanBeActive(bool b)906 void SetCanBeActive(bool b) { m_can_activate = b; }
907
SetDelegate(const WindowDelegateSP & delegate_sp)908 void SetDelegate(const WindowDelegateSP &delegate_sp) {
909 m_delegate_sp = delegate_sp;
910 }
911
GetParent() const912 Window *GetParent() const { return m_parent; }
913
IsActive() const914 bool IsActive() const {
915 if (m_parent)
916 return m_parent->GetActiveWindow().get() == this;
917 else
918 return true; // Top level window is always active
919 }
920
SelectNextWindowAsActive()921 void SelectNextWindowAsActive() {
922 // Move active focus to next window
923 const int num_subwindows = m_subwindows.size();
924 int start_idx = 0;
925 if (m_curr_active_window_idx != UINT32_MAX) {
926 m_prev_active_window_idx = m_curr_active_window_idx;
927 start_idx = m_curr_active_window_idx + 1;
928 }
929 for (int idx = start_idx; idx < num_subwindows; ++idx) {
930 if (m_subwindows[idx]->GetCanBeActive()) {
931 m_curr_active_window_idx = idx;
932 return;
933 }
934 }
935 for (int idx = 0; idx < start_idx; ++idx) {
936 if (m_subwindows[idx]->GetCanBeActive()) {
937 m_curr_active_window_idx = idx;
938 break;
939 }
940 }
941 }
942
SelectPreviousWindowAsActive()943 void SelectPreviousWindowAsActive() {
944 // Move active focus to previous window
945 const int num_subwindows = m_subwindows.size();
946 int start_idx = num_subwindows - 1;
947 if (m_curr_active_window_idx != UINT32_MAX) {
948 m_prev_active_window_idx = m_curr_active_window_idx;
949 start_idx = m_curr_active_window_idx - 1;
950 }
951 for (int idx = start_idx; idx >= 0; --idx) {
952 if (m_subwindows[idx]->GetCanBeActive()) {
953 m_curr_active_window_idx = idx;
954 return;
955 }
956 }
957 for (int idx = num_subwindows - 1; idx > start_idx; --idx) {
958 if (m_subwindows[idx]->GetCanBeActive()) {
959 m_curr_active_window_idx = idx;
960 break;
961 }
962 }
963 }
964
GetName() const965 const char *GetName() const { return m_name.c_str(); }
966
967 protected:
968 std::string m_name;
969 PANEL *m_panel;
970 Window *m_parent;
971 Windows m_subwindows;
972 WindowDelegateSP m_delegate_sp;
973 uint32_t m_curr_active_window_idx;
974 uint32_t m_prev_active_window_idx;
975 bool m_delete;
976 bool m_needs_update;
977 bool m_can_activate;
978 bool m_is_subwin;
979
980 private:
981 Window(const Window &) = delete;
982 const Window &operator=(const Window &) = delete;
983 };
984
985 /////////
986 // Forms
987 /////////
988
989 // A scroll context defines a vertical region that needs to be visible in a
990 // scrolling area. The region is defined by the index of the start and end lines
991 // of the region. The start and end lines may be equal, in which case, the
992 // region is a single line.
993 struct ScrollContext {
994 int start;
995 int end;
996
ScrollContextcurses::ScrollContext997 ScrollContext(int line) : start(line), end(line) {}
ScrollContextcurses::ScrollContext998 ScrollContext(int _start, int _end) : start(_start), end(_end) {}
999
Offsetcurses::ScrollContext1000 void Offset(int offset) {
1001 start += offset;
1002 end += offset;
1003 }
1004 };
1005
1006 class FieldDelegate {
1007 public:
1008 virtual ~FieldDelegate() = default;
1009
1010 // Returns the number of lines needed to draw the field. The draw method will
1011 // be given a surface that have exactly this number of lines.
1012 virtual int FieldDelegateGetHeight() = 0;
1013
1014 // Returns the scroll context in the local coordinates of the field. By
1015 // default, the scroll context spans the whole field. Bigger fields with
1016 // internal navigation should override this method to provide a finer context.
1017 // Typical override methods would first get the scroll context of the internal
1018 // element then add the offset of the element in the field.
FieldDelegateGetScrollContext()1019 virtual ScrollContext FieldDelegateGetScrollContext() {
1020 return ScrollContext(0, FieldDelegateGetHeight() - 1);
1021 }
1022
1023 // Draw the field in the given subpad surface. The surface have a height that
1024 // is equal to the height returned by FieldDelegateGetHeight(). If the field
1025 // is selected in the form window, then is_selected will be true.
1026 virtual void FieldDelegateDraw(Surface &surface, bool is_selected) = 0;
1027
1028 // Handle the key that wasn't handled by the form window or a container field.
FieldDelegateHandleChar(int key)1029 virtual HandleCharResult FieldDelegateHandleChar(int key) {
1030 return eKeyNotHandled;
1031 }
1032
1033 // This is executed once the user exists the field, that is, once the user
1034 // navigates to the next or the previous field. This is particularly useful to
1035 // do in-field validation and error setting. Fields with internal navigation
1036 // should call this method on their fields.
FieldDelegateExitCallback()1037 virtual void FieldDelegateExitCallback() { return; }
1038
1039 // Fields may have internal navigation, for instance, a List Field have
1040 // multiple internal elements, which needs to be navigated. To allow for this
1041 // mechanism, the window shouldn't handle the navigation keys all the time,
1042 // and instead call the key handing method of the selected field. It should
1043 // only handle the navigation keys when the field contains a single element or
1044 // have the last or first element selected depending on if the user is
1045 // navigating forward or backward. Additionally, once a field is selected in
1046 // the forward or backward direction, its first or last internal element
1047 // should be selected. The following methods implements those mechanisms.
1048
1049 // Returns true if the first element in the field is selected or if the field
1050 // contains a single element.
FieldDelegateOnFirstOrOnlyElement()1051 virtual bool FieldDelegateOnFirstOrOnlyElement() { return true; }
1052
1053 // Returns true if the last element in the field is selected or if the field
1054 // contains a single element.
FieldDelegateOnLastOrOnlyElement()1055 virtual bool FieldDelegateOnLastOrOnlyElement() { return true; }
1056
1057 // Select the first element in the field if multiple elements exists.
FieldDelegateSelectFirstElement()1058 virtual void FieldDelegateSelectFirstElement() { return; }
1059
1060 // Select the last element in the field if multiple elements exists.
FieldDelegateSelectLastElement()1061 virtual void FieldDelegateSelectLastElement() { return; }
1062
1063 // Returns true if the field has an error, false otherwise.
FieldDelegateHasError()1064 virtual bool FieldDelegateHasError() { return false; }
1065
FieldDelegateIsVisible()1066 bool FieldDelegateIsVisible() { return m_is_visible; }
1067
FieldDelegateHide()1068 void FieldDelegateHide() { m_is_visible = false; }
1069
FieldDelegateShow()1070 void FieldDelegateShow() { m_is_visible = true; }
1071
1072 protected:
1073 bool m_is_visible = true;
1074 };
1075
1076 typedef std::unique_ptr<FieldDelegate> FieldDelegateUP;
1077
1078 class TextFieldDelegate : public FieldDelegate {
1079 public:
TextFieldDelegate(const char * label,const char * content,bool required)1080 TextFieldDelegate(const char *label, const char *content, bool required)
1081 : m_label(label), m_required(required), m_cursor_position(0),
1082 m_first_visibile_char(0) {
1083 if (content)
1084 m_content = content;
1085 }
1086
1087 // Text fields are drawn as titled boxes of a single line, with a possible
1088 // error messages at the end.
1089 //
1090 // __[Label]___________
1091 // | |
1092 // |__________________|
1093 // - Error message if it exists.
1094
1095 // The text field has a height of 3 lines. 2 lines for borders and 1 line for
1096 // the content.
GetFieldHeight()1097 int GetFieldHeight() { return 3; }
1098
1099 // The text field has a full height of 3 or 4 lines. 3 lines for the actual
1100 // field and an optional line for an error if it exists.
FieldDelegateGetHeight()1101 int FieldDelegateGetHeight() override {
1102 int height = GetFieldHeight();
1103 if (FieldDelegateHasError())
1104 height++;
1105 return height;
1106 }
1107
1108 // Get the cursor X position in the surface coordinate.
GetCursorXPosition()1109 int GetCursorXPosition() { return m_cursor_position - m_first_visibile_char; }
1110
GetContentLength()1111 int GetContentLength() { return m_content.length(); }
1112
DrawContent(Surface & surface,bool is_selected)1113 void DrawContent(Surface &surface, bool is_selected) {
1114 UpdateScrolling(surface.GetWidth());
1115
1116 surface.MoveCursor(0, 0);
1117 const char *text = m_content.c_str() + m_first_visibile_char;
1118 surface.PutCString(text, surface.GetWidth());
1119
1120 // Highlight the cursor.
1121 surface.MoveCursor(GetCursorXPosition(), 0);
1122 if (is_selected)
1123 surface.AttributeOn(A_REVERSE);
1124 if (m_cursor_position == GetContentLength())
1125 // Cursor is past the last character. Highlight an empty space.
1126 surface.PutChar(' ');
1127 else
1128 surface.PutChar(m_content[m_cursor_position]);
1129 if (is_selected)
1130 surface.AttributeOff(A_REVERSE);
1131 }
1132
DrawField(Surface & surface,bool is_selected)1133 void DrawField(Surface &surface, bool is_selected) {
1134 surface.TitledBox(m_label.c_str());
1135
1136 Rect content_bounds = surface.GetFrame();
1137 content_bounds.Inset(1, 1);
1138 Surface content_surface = surface.SubSurface(content_bounds);
1139
1140 DrawContent(content_surface, is_selected);
1141 }
1142
DrawError(Surface & surface)1143 void DrawError(Surface &surface) {
1144 if (!FieldDelegateHasError())
1145 return;
1146 surface.MoveCursor(0, 0);
1147 surface.AttributeOn(COLOR_PAIR(RedOnBlack));
1148 surface.PutChar(ACS_DIAMOND);
1149 surface.PutChar(' ');
1150 surface.PutCStringTruncated(1, GetError().c_str());
1151 surface.AttributeOff(COLOR_PAIR(RedOnBlack));
1152 }
1153
FieldDelegateDraw(Surface & surface,bool is_selected)1154 void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1155 Rect frame = surface.GetFrame();
1156 Rect field_bounds, error_bounds;
1157 frame.HorizontalSplit(GetFieldHeight(), field_bounds, error_bounds);
1158 Surface field_surface = surface.SubSurface(field_bounds);
1159 Surface error_surface = surface.SubSurface(error_bounds);
1160
1161 DrawField(field_surface, is_selected);
1162 DrawError(error_surface);
1163 }
1164
1165 // Get the position of the last visible character.
GetLastVisibleCharPosition(int width)1166 int GetLastVisibleCharPosition(int width) {
1167 int position = m_first_visibile_char + width - 1;
1168 return std::min(position, GetContentLength());
1169 }
1170
UpdateScrolling(int width)1171 void UpdateScrolling(int width) {
1172 if (m_cursor_position < m_first_visibile_char) {
1173 m_first_visibile_char = m_cursor_position;
1174 return;
1175 }
1176
1177 if (m_cursor_position > GetLastVisibleCharPosition(width))
1178 m_first_visibile_char = m_cursor_position - (width - 1);
1179 }
1180
1181 // The cursor is allowed to move one character past the string.
1182 // m_cursor_position is in range [0, GetContentLength()].
MoveCursorRight()1183 void MoveCursorRight() {
1184 if (m_cursor_position < GetContentLength())
1185 m_cursor_position++;
1186 }
1187
MoveCursorLeft()1188 void MoveCursorLeft() {
1189 if (m_cursor_position > 0)
1190 m_cursor_position--;
1191 }
1192
MoveCursorToStart()1193 void MoveCursorToStart() { m_cursor_position = 0; }
1194
MoveCursorToEnd()1195 void MoveCursorToEnd() { m_cursor_position = GetContentLength(); }
1196
ScrollLeft()1197 void ScrollLeft() {
1198 if (m_first_visibile_char > 0)
1199 m_first_visibile_char--;
1200 }
1201
1202 // Insert a character at the current cursor position and advance the cursor
1203 // position.
InsertChar(char character)1204 void InsertChar(char character) {
1205 m_content.insert(m_cursor_position, 1, character);
1206 m_cursor_position++;
1207 ClearError();
1208 }
1209
1210 // Remove the character before the cursor position, retreat the cursor
1211 // position, and scroll left.
RemovePreviousChar()1212 void RemovePreviousChar() {
1213 if (m_cursor_position == 0)
1214 return;
1215
1216 m_content.erase(m_cursor_position - 1, 1);
1217 m_cursor_position--;
1218 ScrollLeft();
1219 ClearError();
1220 }
1221
1222 // Remove the character after the cursor position.
RemoveNextChar()1223 void RemoveNextChar() {
1224 if (m_cursor_position == GetContentLength())
1225 return;
1226
1227 m_content.erase(m_cursor_position, 1);
1228 ClearError();
1229 }
1230
1231 // Clear characters from the current cursor position to the end.
ClearToEnd()1232 void ClearToEnd() {
1233 m_content.erase(m_cursor_position);
1234 ClearError();
1235 }
1236
Clear()1237 void Clear() {
1238 m_content.clear();
1239 m_cursor_position = 0;
1240 ClearError();
1241 }
1242
1243 // True if the key represents a char that can be inserted in the field
1244 // content, false otherwise.
IsAcceptableChar(int key)1245 virtual bool IsAcceptableChar(int key) {
1246 // The behavior of isprint is undefined when the value is not representable
1247 // as an unsigned char. So explicitly check for non-ascii key codes.
1248 if (key > 127)
1249 return false;
1250 return isprint(key);
1251 }
1252
FieldDelegateHandleChar(int key)1253 HandleCharResult FieldDelegateHandleChar(int key) override {
1254 if (IsAcceptableChar(key)) {
1255 ClearError();
1256 InsertChar((char)key);
1257 return eKeyHandled;
1258 }
1259
1260 switch (key) {
1261 case KEY_HOME:
1262 case KEY_CTRL_A:
1263 MoveCursorToStart();
1264 return eKeyHandled;
1265 case KEY_END:
1266 case KEY_CTRL_E:
1267 MoveCursorToEnd();
1268 return eKeyHandled;
1269 case KEY_RIGHT:
1270 case KEY_SF:
1271 MoveCursorRight();
1272 return eKeyHandled;
1273 case KEY_LEFT:
1274 case KEY_SR:
1275 MoveCursorLeft();
1276 return eKeyHandled;
1277 case KEY_BACKSPACE:
1278 case KEY_DELETE:
1279 RemovePreviousChar();
1280 return eKeyHandled;
1281 case KEY_DC:
1282 RemoveNextChar();
1283 return eKeyHandled;
1284 case KEY_EOL:
1285 case KEY_CTRL_K:
1286 ClearToEnd();
1287 return eKeyHandled;
1288 case KEY_DL:
1289 case KEY_CLEAR:
1290 Clear();
1291 return eKeyHandled;
1292 default:
1293 break;
1294 }
1295 return eKeyNotHandled;
1296 }
1297
FieldDelegateHasError()1298 bool FieldDelegateHasError() override { return !m_error.empty(); }
1299
FieldDelegateExitCallback()1300 void FieldDelegateExitCallback() override {
1301 if (!IsSpecified() && m_required)
1302 SetError("This field is required!");
1303 }
1304
IsSpecified()1305 bool IsSpecified() { return !m_content.empty(); }
1306
ClearError()1307 void ClearError() { m_error.clear(); }
1308
GetError()1309 const std::string &GetError() { return m_error; }
1310
SetError(const char * error)1311 void SetError(const char *error) { m_error = error; }
1312
GetText()1313 const std::string &GetText() { return m_content; }
1314
SetText(const char * text)1315 void SetText(const char *text) {
1316 if (text == nullptr) {
1317 m_content.clear();
1318 return;
1319 }
1320 m_content = text;
1321 }
1322
1323 protected:
1324 std::string m_label;
1325 bool m_required;
1326 // The position of the top left corner character of the border.
1327 std::string m_content;
1328 // The cursor position in the content string itself. Can be in the range
1329 // [0, GetContentLength()].
1330 int m_cursor_position;
1331 // The index of the first visible character in the content.
1332 int m_first_visibile_char;
1333 // Optional error message. If empty, field is considered to have no error.
1334 std::string m_error;
1335 };
1336
1337 class IntegerFieldDelegate : public TextFieldDelegate {
1338 public:
IntegerFieldDelegate(const char * label,int content,bool required)1339 IntegerFieldDelegate(const char *label, int content, bool required)
1340 : TextFieldDelegate(label, std::to_string(content).c_str(), required) {}
1341
1342 // Only accept digits.
IsAcceptableChar(int key)1343 bool IsAcceptableChar(int key) override { return isdigit(key); }
1344
1345 // Returns the integer content of the field.
GetInteger()1346 int GetInteger() { return std::stoi(m_content); }
1347 };
1348
1349 class FileFieldDelegate : public TextFieldDelegate {
1350 public:
FileFieldDelegate(const char * label,const char * content,bool need_to_exist,bool required)1351 FileFieldDelegate(const char *label, const char *content, bool need_to_exist,
1352 bool required)
1353 : TextFieldDelegate(label, content, required),
1354 m_need_to_exist(need_to_exist) {}
1355
FieldDelegateExitCallback()1356 void FieldDelegateExitCallback() override {
1357 TextFieldDelegate::FieldDelegateExitCallback();
1358 if (!IsSpecified())
1359 return;
1360
1361 if (!m_need_to_exist)
1362 return;
1363
1364 FileSpec file = GetResolvedFileSpec();
1365 if (!FileSystem::Instance().Exists(file)) {
1366 SetError("File doesn't exist!");
1367 return;
1368 }
1369 if (FileSystem::Instance().IsDirectory(file)) {
1370 SetError("Not a file!");
1371 return;
1372 }
1373 }
1374
GetFileSpec()1375 FileSpec GetFileSpec() {
1376 FileSpec file_spec(GetPath());
1377 return file_spec;
1378 }
1379
GetResolvedFileSpec()1380 FileSpec GetResolvedFileSpec() {
1381 FileSpec file_spec(GetPath());
1382 FileSystem::Instance().Resolve(file_spec);
1383 return file_spec;
1384 }
1385
GetPath()1386 const std::string &GetPath() { return m_content; }
1387
1388 protected:
1389 bool m_need_to_exist;
1390 };
1391
1392 class DirectoryFieldDelegate : public TextFieldDelegate {
1393 public:
DirectoryFieldDelegate(const char * label,const char * content,bool need_to_exist,bool required)1394 DirectoryFieldDelegate(const char *label, const char *content,
1395 bool need_to_exist, bool required)
1396 : TextFieldDelegate(label, content, required),
1397 m_need_to_exist(need_to_exist) {}
1398
FieldDelegateExitCallback()1399 void FieldDelegateExitCallback() override {
1400 TextFieldDelegate::FieldDelegateExitCallback();
1401 if (!IsSpecified())
1402 return;
1403
1404 if (!m_need_to_exist)
1405 return;
1406
1407 FileSpec file = GetResolvedFileSpec();
1408 if (!FileSystem::Instance().Exists(file)) {
1409 SetError("Directory doesn't exist!");
1410 return;
1411 }
1412 if (!FileSystem::Instance().IsDirectory(file)) {
1413 SetError("Not a directory!");
1414 return;
1415 }
1416 }
1417
GetFileSpec()1418 FileSpec GetFileSpec() {
1419 FileSpec file_spec(GetPath());
1420 return file_spec;
1421 }
1422
GetResolvedFileSpec()1423 FileSpec GetResolvedFileSpec() {
1424 FileSpec file_spec(GetPath());
1425 FileSystem::Instance().Resolve(file_spec);
1426 return file_spec;
1427 }
1428
GetPath()1429 const std::string &GetPath() { return m_content; }
1430
1431 protected:
1432 bool m_need_to_exist;
1433 };
1434
1435 class ArchFieldDelegate : public TextFieldDelegate {
1436 public:
ArchFieldDelegate(const char * label,const char * content,bool required)1437 ArchFieldDelegate(const char *label, const char *content, bool required)
1438 : TextFieldDelegate(label, content, required) {}
1439
FieldDelegateExitCallback()1440 void FieldDelegateExitCallback() override {
1441 TextFieldDelegate::FieldDelegateExitCallback();
1442 if (!IsSpecified())
1443 return;
1444
1445 if (!GetArchSpec().IsValid())
1446 SetError("Not a valid arch!");
1447 }
1448
GetArchString()1449 const std::string &GetArchString() { return m_content; }
1450
GetArchSpec()1451 ArchSpec GetArchSpec() { return ArchSpec(GetArchString()); }
1452 };
1453
1454 class BooleanFieldDelegate : public FieldDelegate {
1455 public:
BooleanFieldDelegate(const char * label,bool content)1456 BooleanFieldDelegate(const char *label, bool content)
1457 : m_label(label), m_content(content) {}
1458
1459 // Boolean fields are drawn as checkboxes.
1460 //
1461 // [X] Label or [ ] Label
1462
1463 // Boolean fields are have a single line.
FieldDelegateGetHeight()1464 int FieldDelegateGetHeight() override { return 1; }
1465
FieldDelegateDraw(Surface & surface,bool is_selected)1466 void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1467 surface.MoveCursor(0, 0);
1468 surface.PutChar('[');
1469 if (is_selected)
1470 surface.AttributeOn(A_REVERSE);
1471 surface.PutChar(m_content ? ACS_DIAMOND : ' ');
1472 if (is_selected)
1473 surface.AttributeOff(A_REVERSE);
1474 surface.PutChar(']');
1475 surface.PutChar(' ');
1476 surface.PutCString(m_label.c_str());
1477 }
1478
ToggleContent()1479 void ToggleContent() { m_content = !m_content; }
1480
SetContentToTrue()1481 void SetContentToTrue() { m_content = true; }
1482
SetContentToFalse()1483 void SetContentToFalse() { m_content = false; }
1484
FieldDelegateHandleChar(int key)1485 HandleCharResult FieldDelegateHandleChar(int key) override {
1486 switch (key) {
1487 case 't':
1488 case '1':
1489 SetContentToTrue();
1490 return eKeyHandled;
1491 case 'f':
1492 case '0':
1493 SetContentToFalse();
1494 return eKeyHandled;
1495 case ' ':
1496 case '\r':
1497 case '\n':
1498 case KEY_ENTER:
1499 ToggleContent();
1500 return eKeyHandled;
1501 default:
1502 break;
1503 }
1504 return eKeyNotHandled;
1505 }
1506
1507 // Returns the boolean content of the field.
GetBoolean()1508 bool GetBoolean() { return m_content; }
1509
1510 protected:
1511 std::string m_label;
1512 bool m_content;
1513 };
1514
1515 class ChoicesFieldDelegate : public FieldDelegate {
1516 public:
ChoicesFieldDelegate(const char * label,int number_of_visible_choices,std::vector<std::string> choices)1517 ChoicesFieldDelegate(const char *label, int number_of_visible_choices,
1518 std::vector<std::string> choices)
1519 : m_label(label), m_number_of_visible_choices(number_of_visible_choices),
1520 m_choices(choices), m_choice(0), m_first_visibile_choice(0) {}
1521
1522 // Choices fields are drawn as titles boxses of a number of visible choices.
1523 // The rest of the choices become visible as the user scroll. The selected
1524 // choice is denoted by a diamond as the first character.
1525 //
1526 // __[Label]___________
1527 // |-Choice 1 |
1528 // | Choice 2 |
1529 // | Choice 3 |
1530 // |__________________|
1531
1532 // Choices field have two border characters plus the number of visible
1533 // choices.
FieldDelegateGetHeight()1534 int FieldDelegateGetHeight() override {
1535 return m_number_of_visible_choices + 2;
1536 }
1537
GetNumberOfChoices()1538 int GetNumberOfChoices() { return m_choices.size(); }
1539
1540 // Get the index of the last visible choice.
GetLastVisibleChoice()1541 int GetLastVisibleChoice() {
1542 int index = m_first_visibile_choice + m_number_of_visible_choices;
1543 return std::min(index, GetNumberOfChoices()) - 1;
1544 }
1545
DrawContent(Surface & surface,bool is_selected)1546 void DrawContent(Surface &surface, bool is_selected) {
1547 int choices_to_draw = GetLastVisibleChoice() - m_first_visibile_choice + 1;
1548 for (int i = 0; i < choices_to_draw; i++) {
1549 surface.MoveCursor(0, i);
1550 int current_choice = m_first_visibile_choice + i;
1551 const char *text = m_choices[current_choice].c_str();
1552 bool highlight = is_selected && current_choice == m_choice;
1553 if (highlight)
1554 surface.AttributeOn(A_REVERSE);
1555 surface.PutChar(current_choice == m_choice ? ACS_DIAMOND : ' ');
1556 surface.PutCString(text);
1557 if (highlight)
1558 surface.AttributeOff(A_REVERSE);
1559 }
1560 }
1561
FieldDelegateDraw(Surface & surface,bool is_selected)1562 void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1563 UpdateScrolling();
1564
1565 surface.TitledBox(m_label.c_str());
1566
1567 Rect content_bounds = surface.GetFrame();
1568 content_bounds.Inset(1, 1);
1569 Surface content_surface = surface.SubSurface(content_bounds);
1570
1571 DrawContent(content_surface, is_selected);
1572 }
1573
SelectPrevious()1574 void SelectPrevious() {
1575 if (m_choice > 0)
1576 m_choice--;
1577 }
1578
SelectNext()1579 void SelectNext() {
1580 if (m_choice < GetNumberOfChoices() - 1)
1581 m_choice++;
1582 }
1583
UpdateScrolling()1584 void UpdateScrolling() {
1585 if (m_choice > GetLastVisibleChoice()) {
1586 m_first_visibile_choice = m_choice - (m_number_of_visible_choices - 1);
1587 return;
1588 }
1589
1590 if (m_choice < m_first_visibile_choice)
1591 m_first_visibile_choice = m_choice;
1592 }
1593
FieldDelegateHandleChar(int key)1594 HandleCharResult FieldDelegateHandleChar(int key) override {
1595 switch (key) {
1596 case KEY_UP:
1597 SelectPrevious();
1598 return eKeyHandled;
1599 case KEY_DOWN:
1600 SelectNext();
1601 return eKeyHandled;
1602 default:
1603 break;
1604 }
1605 return eKeyNotHandled;
1606 }
1607
1608 // Returns the content of the choice as a string.
GetChoiceContent()1609 std::string GetChoiceContent() { return m_choices[m_choice]; }
1610
1611 // Returns the index of the choice.
GetChoice()1612 int GetChoice() { return m_choice; }
1613
SetChoice(const std::string & choice)1614 void SetChoice(const std::string &choice) {
1615 for (int i = 0; i < GetNumberOfChoices(); i++) {
1616 if (choice == m_choices[i]) {
1617 m_choice = i;
1618 return;
1619 }
1620 }
1621 }
1622
1623 protected:
1624 std::string m_label;
1625 int m_number_of_visible_choices;
1626 std::vector<std::string> m_choices;
1627 // The index of the selected choice.
1628 int m_choice;
1629 // The index of the first visible choice in the field.
1630 int m_first_visibile_choice;
1631 };
1632
1633 class PlatformPluginFieldDelegate : public ChoicesFieldDelegate {
1634 public:
PlatformPluginFieldDelegate(Debugger & debugger)1635 PlatformPluginFieldDelegate(Debugger &debugger)
1636 : ChoicesFieldDelegate("Platform Plugin", 3, GetPossiblePluginNames()) {
1637 PlatformSP platform_sp = debugger.GetPlatformList().GetSelectedPlatform();
1638 if (platform_sp)
1639 SetChoice(platform_sp->GetName().AsCString());
1640 }
1641
GetPossiblePluginNames()1642 std::vector<std::string> GetPossiblePluginNames() {
1643 std::vector<std::string> names;
1644 size_t i = 0;
1645 while (auto name = PluginManager::GetPlatformPluginNameAtIndex(i++))
1646 names.push_back(name);
1647 return names;
1648 }
1649
GetPluginName()1650 std::string GetPluginName() {
1651 std::string plugin_name = GetChoiceContent();
1652 return plugin_name;
1653 }
1654 };
1655
1656 class ProcessPluginFieldDelegate : public ChoicesFieldDelegate {
1657 public:
ProcessPluginFieldDelegate()1658 ProcessPluginFieldDelegate()
1659 : ChoicesFieldDelegate("Process Plugin", 3, GetPossiblePluginNames()) {}
1660
GetPossiblePluginNames()1661 std::vector<std::string> GetPossiblePluginNames() {
1662 std::vector<std::string> names;
1663 names.push_back("<default>");
1664
1665 size_t i = 0;
1666 while (auto name = PluginManager::GetProcessPluginNameAtIndex(i++))
1667 names.push_back(name);
1668 return names;
1669 }
1670
GetPluginName()1671 std::string GetPluginName() {
1672 std::string plugin_name = GetChoiceContent();
1673 if (plugin_name == "<default>")
1674 return "";
1675 return plugin_name;
1676 }
1677 };
1678
1679 class LazyBooleanFieldDelegate : public ChoicesFieldDelegate {
1680 public:
LazyBooleanFieldDelegate(const char * label,const char * calculate_label)1681 LazyBooleanFieldDelegate(const char *label, const char *calculate_label)
1682 : ChoicesFieldDelegate(label, 3, GetPossibleOptions(calculate_label)) {}
1683
1684 static constexpr const char *kNo = "No";
1685 static constexpr const char *kYes = "Yes";
1686
GetPossibleOptions(const char * calculate_label)1687 std::vector<std::string> GetPossibleOptions(const char *calculate_label) {
1688 std::vector<std::string> options;
1689 options.push_back(calculate_label);
1690 options.push_back(kYes);
1691 options.push_back(kNo);
1692 return options;
1693 }
1694
GetLazyBoolean()1695 LazyBool GetLazyBoolean() {
1696 std::string choice = GetChoiceContent();
1697 if (choice == kNo)
1698 return eLazyBoolNo;
1699 else if (choice == kYes)
1700 return eLazyBoolYes;
1701 else
1702 return eLazyBoolCalculate;
1703 }
1704 };
1705
1706 template <class T> class ListFieldDelegate : public FieldDelegate {
1707 public:
ListFieldDelegate(const char * label,T default_field)1708 ListFieldDelegate(const char *label, T default_field)
1709 : m_label(label), m_default_field(default_field), m_selection_index(0),
1710 m_selection_type(SelectionType::NewButton) {}
1711
1712 // Signify which element is selected. If a field or a remove button is
1713 // selected, then m_selection_index signifies the particular field that
1714 // is selected or the field that the remove button belongs to.
1715 enum class SelectionType { Field, RemoveButton, NewButton };
1716
1717 // A List field is drawn as a titled box of a number of other fields of the
1718 // same type. Each field has a Remove button next to it that removes the
1719 // corresponding field. Finally, the last line contains a New button to add a
1720 // new field.
1721 //
1722 // __[Label]___________
1723 // | Field 0 [Remove] |
1724 // | Field 1 [Remove] |
1725 // | Field 2 [Remove] |
1726 // | [New] |
1727 // |__________________|
1728
1729 // List fields have two lines for border characters, 1 line for the New
1730 // button, and the total height of the available fields.
FieldDelegateGetHeight()1731 int FieldDelegateGetHeight() override {
1732 // 2 border characters.
1733 int height = 2;
1734 // Total height of the fields.
1735 for (int i = 0; i < GetNumberOfFields(); i++) {
1736 height += m_fields[i].FieldDelegateGetHeight();
1737 }
1738 // A line for the New button.
1739 height++;
1740 return height;
1741 }
1742
FieldDelegateGetScrollContext()1743 ScrollContext FieldDelegateGetScrollContext() override {
1744 int height = FieldDelegateGetHeight();
1745 if (m_selection_type == SelectionType::NewButton)
1746 return ScrollContext(height - 2, height - 1);
1747
1748 FieldDelegate &field = m_fields[m_selection_index];
1749 ScrollContext context = field.FieldDelegateGetScrollContext();
1750
1751 // Start at 1 because of the top border.
1752 int offset = 1;
1753 for (int i = 0; i < m_selection_index; i++) {
1754 offset += m_fields[i].FieldDelegateGetHeight();
1755 }
1756 context.Offset(offset);
1757
1758 // If the scroll context is touching the top border, include it in the
1759 // context to show the label.
1760 if (context.start == 1)
1761 context.start--;
1762
1763 // If the scroll context is touching the new button, include it as well as
1764 // the bottom border in the context.
1765 if (context.end == height - 3)
1766 context.end += 2;
1767
1768 return context;
1769 }
1770
DrawRemoveButton(Surface & surface,int highlight)1771 void DrawRemoveButton(Surface &surface, int highlight) {
1772 surface.MoveCursor(1, surface.GetHeight() / 2);
1773 if (highlight)
1774 surface.AttributeOn(A_REVERSE);
1775 surface.PutCString("[Remove]");
1776 if (highlight)
1777 surface.AttributeOff(A_REVERSE);
1778 }
1779
DrawFields(Surface & surface,bool is_selected)1780 void DrawFields(Surface &surface, bool is_selected) {
1781 int line = 0;
1782 int width = surface.GetWidth();
1783 for (int i = 0; i < GetNumberOfFields(); i++) {
1784 int height = m_fields[i].FieldDelegateGetHeight();
1785 Rect bounds = Rect(Point(0, line), Size(width, height));
1786 Rect field_bounds, remove_button_bounds;
1787 bounds.VerticalSplit(bounds.size.width - sizeof(" [Remove]"),
1788 field_bounds, remove_button_bounds);
1789 Surface field_surface = surface.SubSurface(field_bounds);
1790 Surface remove_button_surface = surface.SubSurface(remove_button_bounds);
1791
1792 bool is_element_selected = m_selection_index == i && is_selected;
1793 bool is_field_selected =
1794 is_element_selected && m_selection_type == SelectionType::Field;
1795 bool is_remove_button_selected =
1796 is_element_selected &&
1797 m_selection_type == SelectionType::RemoveButton;
1798 m_fields[i].FieldDelegateDraw(field_surface, is_field_selected);
1799 DrawRemoveButton(remove_button_surface, is_remove_button_selected);
1800
1801 line += height;
1802 }
1803 }
1804
DrawNewButton(Surface & surface,bool is_selected)1805 void DrawNewButton(Surface &surface, bool is_selected) {
1806 const char *button_text = "[New]";
1807 int x = (surface.GetWidth() - sizeof(button_text) - 1) / 2;
1808 surface.MoveCursor(x, 0);
1809 bool highlight =
1810 is_selected && m_selection_type == SelectionType::NewButton;
1811 if (highlight)
1812 surface.AttributeOn(A_REVERSE);
1813 surface.PutCString(button_text);
1814 if (highlight)
1815 surface.AttributeOff(A_REVERSE);
1816 }
1817
FieldDelegateDraw(Surface & surface,bool is_selected)1818 void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1819 surface.TitledBox(m_label.c_str());
1820
1821 Rect content_bounds = surface.GetFrame();
1822 content_bounds.Inset(1, 1);
1823 Rect fields_bounds, new_button_bounds;
1824 content_bounds.HorizontalSplit(content_bounds.size.height - 1,
1825 fields_bounds, new_button_bounds);
1826 Surface fields_surface = surface.SubSurface(fields_bounds);
1827 Surface new_button_surface = surface.SubSurface(new_button_bounds);
1828
1829 DrawFields(fields_surface, is_selected);
1830 DrawNewButton(new_button_surface, is_selected);
1831 }
1832
AddNewField()1833 void AddNewField() {
1834 m_fields.push_back(m_default_field);
1835 m_selection_index = GetNumberOfFields() - 1;
1836 m_selection_type = SelectionType::Field;
1837 FieldDelegate &field = m_fields[m_selection_index];
1838 field.FieldDelegateSelectFirstElement();
1839 }
1840
RemoveField()1841 void RemoveField() {
1842 m_fields.erase(m_fields.begin() + m_selection_index);
1843 if (m_selection_index != 0)
1844 m_selection_index--;
1845
1846 if (GetNumberOfFields() > 0) {
1847 m_selection_type = SelectionType::Field;
1848 FieldDelegate &field = m_fields[m_selection_index];
1849 field.FieldDelegateSelectFirstElement();
1850 } else
1851 m_selection_type = SelectionType::NewButton;
1852 }
1853
SelectNext(int key)1854 HandleCharResult SelectNext(int key) {
1855 if (m_selection_type == SelectionType::NewButton)
1856 return eKeyNotHandled;
1857
1858 if (m_selection_type == SelectionType::RemoveButton) {
1859 if (m_selection_index == GetNumberOfFields() - 1) {
1860 m_selection_type = SelectionType::NewButton;
1861 return eKeyHandled;
1862 }
1863 m_selection_index++;
1864 m_selection_type = SelectionType::Field;
1865 FieldDelegate &next_field = m_fields[m_selection_index];
1866 next_field.FieldDelegateSelectFirstElement();
1867 return eKeyHandled;
1868 }
1869
1870 FieldDelegate &field = m_fields[m_selection_index];
1871 if (!field.FieldDelegateOnLastOrOnlyElement()) {
1872 return field.FieldDelegateHandleChar(key);
1873 }
1874
1875 field.FieldDelegateExitCallback();
1876
1877 m_selection_type = SelectionType::RemoveButton;
1878 return eKeyHandled;
1879 }
1880
SelectPrevious(int key)1881 HandleCharResult SelectPrevious(int key) {
1882 if (FieldDelegateOnFirstOrOnlyElement())
1883 return eKeyNotHandled;
1884
1885 if (m_selection_type == SelectionType::RemoveButton) {
1886 m_selection_type = SelectionType::Field;
1887 FieldDelegate &field = m_fields[m_selection_index];
1888 field.FieldDelegateSelectLastElement();
1889 return eKeyHandled;
1890 }
1891
1892 if (m_selection_type == SelectionType::NewButton) {
1893 m_selection_type = SelectionType::RemoveButton;
1894 m_selection_index = GetNumberOfFields() - 1;
1895 return eKeyHandled;
1896 }
1897
1898 FieldDelegate &field = m_fields[m_selection_index];
1899 if (!field.FieldDelegateOnFirstOrOnlyElement()) {
1900 return field.FieldDelegateHandleChar(key);
1901 }
1902
1903 field.FieldDelegateExitCallback();
1904
1905 m_selection_type = SelectionType::RemoveButton;
1906 m_selection_index--;
1907 return eKeyHandled;
1908 }
1909
1910 // If the last element of the field is selected and it didn't handle the key.
1911 // Select the next field or new button if the selected field is the last one.
SelectNextInList(int key)1912 HandleCharResult SelectNextInList(int key) {
1913 assert(m_selection_type == SelectionType::Field);
1914
1915 FieldDelegate &field = m_fields[m_selection_index];
1916 if (field.FieldDelegateHandleChar(key) == eKeyHandled)
1917 return eKeyHandled;
1918
1919 if (!field.FieldDelegateOnLastOrOnlyElement())
1920 return eKeyNotHandled;
1921
1922 field.FieldDelegateExitCallback();
1923
1924 if (m_selection_index == GetNumberOfFields() - 1) {
1925 m_selection_type = SelectionType::NewButton;
1926 return eKeyHandled;
1927 }
1928
1929 m_selection_index++;
1930 FieldDelegate &next_field = m_fields[m_selection_index];
1931 next_field.FieldDelegateSelectFirstElement();
1932 return eKeyHandled;
1933 }
1934
FieldDelegateHandleChar(int key)1935 HandleCharResult FieldDelegateHandleChar(int key) override {
1936 switch (key) {
1937 case '\r':
1938 case '\n':
1939 case KEY_ENTER:
1940 switch (m_selection_type) {
1941 case SelectionType::NewButton:
1942 AddNewField();
1943 return eKeyHandled;
1944 case SelectionType::RemoveButton:
1945 RemoveField();
1946 return eKeyHandled;
1947 case SelectionType::Field:
1948 return SelectNextInList(key);
1949 }
1950 break;
1951 case '\t':
1952 return SelectNext(key);
1953 case KEY_SHIFT_TAB:
1954 return SelectPrevious(key);
1955 default:
1956 break;
1957 }
1958
1959 // If the key wasn't handled and one of the fields is selected, pass the key
1960 // to that field.
1961 if (m_selection_type == SelectionType::Field) {
1962 return m_fields[m_selection_index].FieldDelegateHandleChar(key);
1963 }
1964
1965 return eKeyNotHandled;
1966 }
1967
FieldDelegateOnLastOrOnlyElement()1968 bool FieldDelegateOnLastOrOnlyElement() override {
1969 if (m_selection_type == SelectionType::NewButton) {
1970 return true;
1971 }
1972 return false;
1973 }
1974
FieldDelegateOnFirstOrOnlyElement()1975 bool FieldDelegateOnFirstOrOnlyElement() override {
1976 if (m_selection_type == SelectionType::NewButton &&
1977 GetNumberOfFields() == 0)
1978 return true;
1979
1980 if (m_selection_type == SelectionType::Field && m_selection_index == 0) {
1981 FieldDelegate &field = m_fields[m_selection_index];
1982 return field.FieldDelegateOnFirstOrOnlyElement();
1983 }
1984
1985 return false;
1986 }
1987
FieldDelegateSelectFirstElement()1988 void FieldDelegateSelectFirstElement() override {
1989 if (GetNumberOfFields() == 0) {
1990 m_selection_type = SelectionType::NewButton;
1991 return;
1992 }
1993
1994 m_selection_type = SelectionType::Field;
1995 m_selection_index = 0;
1996 }
1997
FieldDelegateSelectLastElement()1998 void FieldDelegateSelectLastElement() override {
1999 m_selection_type = SelectionType::NewButton;
2000 return;
2001 }
2002
GetNumberOfFields()2003 int GetNumberOfFields() { return m_fields.size(); }
2004
2005 // Returns the form delegate at the current index.
GetField(int index)2006 T &GetField(int index) { return m_fields[index]; }
2007
2008 protected:
2009 std::string m_label;
2010 // The default field delegate instance from which new field delegates will be
2011 // created though a copy.
2012 T m_default_field;
2013 std::vector<T> m_fields;
2014 int m_selection_index;
2015 // See SelectionType class enum.
2016 SelectionType m_selection_type;
2017 };
2018
2019 class ArgumentsFieldDelegate : public ListFieldDelegate<TextFieldDelegate> {
2020 public:
ArgumentsFieldDelegate()2021 ArgumentsFieldDelegate()
2022 : ListFieldDelegate("Arguments",
2023 TextFieldDelegate("Argument", "", false)) {}
2024
GetArguments()2025 Args GetArguments() {
2026 Args arguments;
2027 for (int i = 0; i < GetNumberOfFields(); i++) {
2028 arguments.AppendArgument(GetField(i).GetText());
2029 }
2030 return arguments;
2031 }
2032
AddArguments(const Args & arguments)2033 void AddArguments(const Args &arguments) {
2034 for (size_t i = 0; i < arguments.GetArgumentCount(); i++) {
2035 AddNewField();
2036 TextFieldDelegate &field = GetField(GetNumberOfFields() - 1);
2037 field.SetText(arguments.GetArgumentAtIndex(i));
2038 }
2039 }
2040 };
2041
2042 template <class KeyFieldDelegateType, class ValueFieldDelegateType>
2043 class MappingFieldDelegate : public FieldDelegate {
2044 public:
MappingFieldDelegate(KeyFieldDelegateType key_field,ValueFieldDelegateType value_field)2045 MappingFieldDelegate(KeyFieldDelegateType key_field,
2046 ValueFieldDelegateType value_field)
2047 : m_key_field(key_field), m_value_field(value_field),
2048 m_selection_type(SelectionType::Key) {}
2049
2050 // Signify which element is selected. The key field or its value field.
2051 enum class SelectionType { Key, Value };
2052
2053 // A mapping field is drawn as two text fields with a right arrow in between.
2054 // The first field stores the key of the mapping and the second stores the
2055 // value if the mapping.
2056 //
2057 // __[Key]_____________ __[Value]___________
2058 // | | > | |
2059 // |__________________| |__________________|
2060 // - Error message if it exists.
2061
2062 // The mapping field has a height that is equal to the maximum height between
2063 // the key and value fields.
FieldDelegateGetHeight()2064 int FieldDelegateGetHeight() override {
2065 return std::max(m_key_field.FieldDelegateGetHeight(),
2066 m_value_field.FieldDelegateGetHeight());
2067 }
2068
DrawArrow(Surface & surface)2069 void DrawArrow(Surface &surface) {
2070 surface.MoveCursor(0, 1);
2071 surface.PutChar(ACS_RARROW);
2072 }
2073
FieldDelegateDraw(Surface & surface,bool is_selected)2074 void FieldDelegateDraw(Surface &surface, bool is_selected) override {
2075 Rect bounds = surface.GetFrame();
2076 Rect key_field_bounds, arrow_and_value_field_bounds;
2077 bounds.VerticalSplit(bounds.size.width / 2, key_field_bounds,
2078 arrow_and_value_field_bounds);
2079 Rect arrow_bounds, value_field_bounds;
2080 arrow_and_value_field_bounds.VerticalSplit(1, arrow_bounds,
2081 value_field_bounds);
2082
2083 Surface key_field_surface = surface.SubSurface(key_field_bounds);
2084 Surface arrow_surface = surface.SubSurface(arrow_bounds);
2085 Surface value_field_surface = surface.SubSurface(value_field_bounds);
2086
2087 bool key_is_selected =
2088 m_selection_type == SelectionType::Key && is_selected;
2089 m_key_field.FieldDelegateDraw(key_field_surface, key_is_selected);
2090 DrawArrow(arrow_surface);
2091 bool value_is_selected =
2092 m_selection_type == SelectionType::Value && is_selected;
2093 m_value_field.FieldDelegateDraw(value_field_surface, value_is_selected);
2094 }
2095
SelectNext(int key)2096 HandleCharResult SelectNext(int key) {
2097 if (FieldDelegateOnLastOrOnlyElement())
2098 return eKeyNotHandled;
2099
2100 if (!m_key_field.FieldDelegateOnLastOrOnlyElement()) {
2101 return m_key_field.FieldDelegateHandleChar(key);
2102 }
2103
2104 m_key_field.FieldDelegateExitCallback();
2105 m_selection_type = SelectionType::Value;
2106 m_value_field.FieldDelegateSelectFirstElement();
2107 return eKeyHandled;
2108 }
2109
SelectPrevious(int key)2110 HandleCharResult SelectPrevious(int key) {
2111 if (FieldDelegateOnFirstOrOnlyElement())
2112 return eKeyNotHandled;
2113
2114 if (!m_value_field.FieldDelegateOnFirstOrOnlyElement()) {
2115 return m_value_field.FieldDelegateHandleChar(key);
2116 }
2117
2118 m_value_field.FieldDelegateExitCallback();
2119 m_selection_type = SelectionType::Key;
2120 m_key_field.FieldDelegateSelectLastElement();
2121 return eKeyHandled;
2122 }
2123
2124 // If the value field is selected, pass the key to it. If the key field is
2125 // selected, its last element is selected, and it didn't handle the key, then
2126 // select its corresponding value field.
SelectNextField(int key)2127 HandleCharResult SelectNextField(int key) {
2128 if (m_selection_type == SelectionType::Value) {
2129 return m_value_field.FieldDelegateHandleChar(key);
2130 }
2131
2132 if (m_key_field.FieldDelegateHandleChar(key) == eKeyHandled)
2133 return eKeyHandled;
2134
2135 if (!m_key_field.FieldDelegateOnLastOrOnlyElement())
2136 return eKeyNotHandled;
2137
2138 m_key_field.FieldDelegateExitCallback();
2139 m_selection_type = SelectionType::Value;
2140 m_value_field.FieldDelegateSelectFirstElement();
2141 return eKeyHandled;
2142 }
2143
FieldDelegateHandleChar(int key)2144 HandleCharResult FieldDelegateHandleChar(int key) override {
2145 switch (key) {
2146 case KEY_RETURN:
2147 return SelectNextField(key);
2148 case '\t':
2149 return SelectNext(key);
2150 case KEY_SHIFT_TAB:
2151 return SelectPrevious(key);
2152 default:
2153 break;
2154 }
2155
2156 // If the key wasn't handled, pass the key to the selected field.
2157 if (m_selection_type == SelectionType::Key)
2158 return m_key_field.FieldDelegateHandleChar(key);
2159 else
2160 return m_value_field.FieldDelegateHandleChar(key);
2161
2162 return eKeyNotHandled;
2163 }
2164
FieldDelegateOnFirstOrOnlyElement()2165 bool FieldDelegateOnFirstOrOnlyElement() override {
2166 return m_selection_type == SelectionType::Key;
2167 }
2168
FieldDelegateOnLastOrOnlyElement()2169 bool FieldDelegateOnLastOrOnlyElement() override {
2170 return m_selection_type == SelectionType::Value;
2171 }
2172
FieldDelegateSelectFirstElement()2173 void FieldDelegateSelectFirstElement() override {
2174 m_selection_type = SelectionType::Key;
2175 }
2176
FieldDelegateSelectLastElement()2177 void FieldDelegateSelectLastElement() override {
2178 m_selection_type = SelectionType::Value;
2179 }
2180
FieldDelegateHasError()2181 bool FieldDelegateHasError() override {
2182 return m_key_field.FieldDelegateHasError() ||
2183 m_value_field.FieldDelegateHasError();
2184 }
2185
GetKeyField()2186 KeyFieldDelegateType &GetKeyField() { return m_key_field; }
2187
GetValueField()2188 ValueFieldDelegateType &GetValueField() { return m_value_field; }
2189
2190 protected:
2191 KeyFieldDelegateType m_key_field;
2192 ValueFieldDelegateType m_value_field;
2193 // See SelectionType class enum.
2194 SelectionType m_selection_type;
2195 };
2196
2197 class EnvironmentVariableNameFieldDelegate : public TextFieldDelegate {
2198 public:
EnvironmentVariableNameFieldDelegate(const char * content)2199 EnvironmentVariableNameFieldDelegate(const char *content)
2200 : TextFieldDelegate("Name", content, true) {}
2201
2202 // Environment variable names can't contain an equal sign.
IsAcceptableChar(int key)2203 bool IsAcceptableChar(int key) override {
2204 return TextFieldDelegate::IsAcceptableChar(key) && key != '=';
2205 }
2206
GetName()2207 const std::string &GetName() { return m_content; }
2208 };
2209
2210 class EnvironmentVariableFieldDelegate
2211 : public MappingFieldDelegate<EnvironmentVariableNameFieldDelegate,
2212 TextFieldDelegate> {
2213 public:
EnvironmentVariableFieldDelegate()2214 EnvironmentVariableFieldDelegate()
2215 : MappingFieldDelegate(
2216 EnvironmentVariableNameFieldDelegate(""),
2217 TextFieldDelegate("Value", "", /*required=*/false)) {}
2218
GetName()2219 const std::string &GetName() { return GetKeyField().GetName(); }
2220
GetValue()2221 const std::string &GetValue() { return GetValueField().GetText(); }
2222
SetName(const char * name)2223 void SetName(const char *name) { return GetKeyField().SetText(name); }
2224
SetValue(const char * value)2225 void SetValue(const char *value) { return GetValueField().SetText(value); }
2226 };
2227
2228 class EnvironmentVariableListFieldDelegate
2229 : public ListFieldDelegate<EnvironmentVariableFieldDelegate> {
2230 public:
EnvironmentVariableListFieldDelegate(const char * label)2231 EnvironmentVariableListFieldDelegate(const char *label)
2232 : ListFieldDelegate(label, EnvironmentVariableFieldDelegate()) {}
2233
GetEnvironment()2234 Environment GetEnvironment() {
2235 Environment environment;
2236 for (int i = 0; i < GetNumberOfFields(); i++) {
2237 environment.insert(
2238 std::make_pair(GetField(i).GetName(), GetField(i).GetValue()));
2239 }
2240 return environment;
2241 }
2242
AddEnvironmentVariables(const Environment & environment)2243 void AddEnvironmentVariables(const Environment &environment) {
2244 for (auto &variable : environment) {
2245 AddNewField();
2246 EnvironmentVariableFieldDelegate &field =
2247 GetField(GetNumberOfFields() - 1);
2248 field.SetName(variable.getKey().str().c_str());
2249 field.SetValue(variable.getValue().c_str());
2250 }
2251 }
2252 };
2253
2254 class FormAction {
2255 public:
FormAction(const char * label,std::function<void (Window &)> action)2256 FormAction(const char *label, std::function<void(Window &)> action)
2257 : m_action(action) {
2258 if (label)
2259 m_label = label;
2260 }
2261
2262 // Draw a centered [Label].
Draw(Surface & surface,bool is_selected)2263 void Draw(Surface &surface, bool is_selected) {
2264 int x = (surface.GetWidth() - m_label.length()) / 2;
2265 surface.MoveCursor(x, 0);
2266 if (is_selected)
2267 surface.AttributeOn(A_REVERSE);
2268 surface.PutChar('[');
2269 surface.PutCString(m_label.c_str());
2270 surface.PutChar(']');
2271 if (is_selected)
2272 surface.AttributeOff(A_REVERSE);
2273 }
2274
Execute(Window & window)2275 void Execute(Window &window) { m_action(window); }
2276
GetLabel()2277 const std::string &GetLabel() { return m_label; }
2278
2279 protected:
2280 std::string m_label;
2281 std::function<void(Window &)> m_action;
2282 };
2283
2284 class FormDelegate {
2285 public:
FormDelegate()2286 FormDelegate() {}
2287
2288 virtual ~FormDelegate() = default;
2289
2290 virtual std::string GetName() = 0;
2291
UpdateFieldsVisibility()2292 virtual void UpdateFieldsVisibility() { return; }
2293
GetField(uint32_t field_index)2294 FieldDelegate *GetField(uint32_t field_index) {
2295 if (field_index < m_fields.size())
2296 return m_fields[field_index].get();
2297 return nullptr;
2298 }
2299
GetAction(int action_index)2300 FormAction &GetAction(int action_index) { return m_actions[action_index]; }
2301
GetNumberOfFields()2302 int GetNumberOfFields() { return m_fields.size(); }
2303
GetNumberOfActions()2304 int GetNumberOfActions() { return m_actions.size(); }
2305
HasError()2306 bool HasError() { return !m_error.empty(); }
2307
ClearError()2308 void ClearError() { m_error.clear(); }
2309
GetError()2310 const std::string &GetError() { return m_error; }
2311
SetError(const char * error)2312 void SetError(const char *error) { m_error = error; }
2313
2314 // If all fields are valid, true is returned. Otherwise, an error message is
2315 // set and false is returned. This method is usually called at the start of an
2316 // action that requires valid fields.
CheckFieldsValidity()2317 bool CheckFieldsValidity() {
2318 for (int i = 0; i < GetNumberOfFields(); i++) {
2319 GetField(i)->FieldDelegateExitCallback();
2320 if (GetField(i)->FieldDelegateHasError()) {
2321 SetError("Some fields are invalid!");
2322 return false;
2323 }
2324 }
2325 return true;
2326 }
2327
2328 // Factory methods to create and add fields of specific types.
2329
AddTextField(const char * label,const char * content,bool required)2330 TextFieldDelegate *AddTextField(const char *label, const char *content,
2331 bool required) {
2332 TextFieldDelegate *delegate =
2333 new TextFieldDelegate(label, content, required);
2334 m_fields.push_back(FieldDelegateUP(delegate));
2335 return delegate;
2336 }
2337
AddFileField(const char * label,const char * content,bool need_to_exist,bool required)2338 FileFieldDelegate *AddFileField(const char *label, const char *content,
2339 bool need_to_exist, bool required) {
2340 FileFieldDelegate *delegate =
2341 new FileFieldDelegate(label, content, need_to_exist, required);
2342 m_fields.push_back(FieldDelegateUP(delegate));
2343 return delegate;
2344 }
2345
AddDirectoryField(const char * label,const char * content,bool need_to_exist,bool required)2346 DirectoryFieldDelegate *AddDirectoryField(const char *label,
2347 const char *content,
2348 bool need_to_exist, bool required) {
2349 DirectoryFieldDelegate *delegate =
2350 new DirectoryFieldDelegate(label, content, need_to_exist, required);
2351 m_fields.push_back(FieldDelegateUP(delegate));
2352 return delegate;
2353 }
2354
AddArchField(const char * label,const char * content,bool required)2355 ArchFieldDelegate *AddArchField(const char *label, const char *content,
2356 bool required) {
2357 ArchFieldDelegate *delegate =
2358 new ArchFieldDelegate(label, content, required);
2359 m_fields.push_back(FieldDelegateUP(delegate));
2360 return delegate;
2361 }
2362
AddIntegerField(const char * label,int content,bool required)2363 IntegerFieldDelegate *AddIntegerField(const char *label, int content,
2364 bool required) {
2365 IntegerFieldDelegate *delegate =
2366 new IntegerFieldDelegate(label, content, required);
2367 m_fields.push_back(FieldDelegateUP(delegate));
2368 return delegate;
2369 }
2370
AddBooleanField(const char * label,bool content)2371 BooleanFieldDelegate *AddBooleanField(const char *label, bool content) {
2372 BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content);
2373 m_fields.push_back(FieldDelegateUP(delegate));
2374 return delegate;
2375 }
2376
AddLazyBooleanField(const char * label,const char * calculate_label)2377 LazyBooleanFieldDelegate *AddLazyBooleanField(const char *label,
2378 const char *calculate_label) {
2379 LazyBooleanFieldDelegate *delegate =
2380 new LazyBooleanFieldDelegate(label, calculate_label);
2381 m_fields.push_back(FieldDelegateUP(delegate));
2382 return delegate;
2383 }
2384
AddChoicesField(const char * label,int height,std::vector<std::string> choices)2385 ChoicesFieldDelegate *AddChoicesField(const char *label, int height,
2386 std::vector<std::string> choices) {
2387 ChoicesFieldDelegate *delegate =
2388 new ChoicesFieldDelegate(label, height, choices);
2389 m_fields.push_back(FieldDelegateUP(delegate));
2390 return delegate;
2391 }
2392
AddPlatformPluginField(Debugger & debugger)2393 PlatformPluginFieldDelegate *AddPlatformPluginField(Debugger &debugger) {
2394 PlatformPluginFieldDelegate *delegate =
2395 new PlatformPluginFieldDelegate(debugger);
2396 m_fields.push_back(FieldDelegateUP(delegate));
2397 return delegate;
2398 }
2399
AddProcessPluginField()2400 ProcessPluginFieldDelegate *AddProcessPluginField() {
2401 ProcessPluginFieldDelegate *delegate = new ProcessPluginFieldDelegate();
2402 m_fields.push_back(FieldDelegateUP(delegate));
2403 return delegate;
2404 }
2405
2406 template <class T>
AddListField(const char * label,T default_field)2407 ListFieldDelegate<T> *AddListField(const char *label, T default_field) {
2408 ListFieldDelegate<T> *delegate =
2409 new ListFieldDelegate<T>(label, default_field);
2410 m_fields.push_back(FieldDelegateUP(delegate));
2411 return delegate;
2412 }
2413
AddArgumentsField()2414 ArgumentsFieldDelegate *AddArgumentsField() {
2415 ArgumentsFieldDelegate *delegate = new ArgumentsFieldDelegate();
2416 m_fields.push_back(FieldDelegateUP(delegate));
2417 return delegate;
2418 }
2419
2420 template <class K, class V>
AddMappingField(K key_field,V value_field)2421 MappingFieldDelegate<K, V> *AddMappingField(K key_field, V value_field) {
2422 MappingFieldDelegate<K, V> *delegate =
2423 new MappingFieldDelegate<K, V>(key_field, value_field);
2424 m_fields.push_back(FieldDelegateUP(delegate));
2425 return delegate;
2426 }
2427
2428 EnvironmentVariableNameFieldDelegate *
AddEnvironmentVariableNameField(const char * content)2429 AddEnvironmentVariableNameField(const char *content) {
2430 EnvironmentVariableNameFieldDelegate *delegate =
2431 new EnvironmentVariableNameFieldDelegate(content);
2432 m_fields.push_back(FieldDelegateUP(delegate));
2433 return delegate;
2434 }
2435
AddEnvironmentVariableField()2436 EnvironmentVariableFieldDelegate *AddEnvironmentVariableField() {
2437 EnvironmentVariableFieldDelegate *delegate =
2438 new EnvironmentVariableFieldDelegate();
2439 m_fields.push_back(FieldDelegateUP(delegate));
2440 return delegate;
2441 }
2442
2443 EnvironmentVariableListFieldDelegate *
AddEnvironmentVariableListField(const char * label)2444 AddEnvironmentVariableListField(const char *label) {
2445 EnvironmentVariableListFieldDelegate *delegate =
2446 new EnvironmentVariableListFieldDelegate(label);
2447 m_fields.push_back(FieldDelegateUP(delegate));
2448 return delegate;
2449 }
2450
2451 // Factory methods for adding actions.
2452
AddAction(const char * label,std::function<void (Window &)> action)2453 void AddAction(const char *label, std::function<void(Window &)> action) {
2454 m_actions.push_back(FormAction(label, action));
2455 }
2456
2457 protected:
2458 std::vector<FieldDelegateUP> m_fields;
2459 std::vector<FormAction> m_actions;
2460 // Optional error message. If empty, form is considered to have no error.
2461 std::string m_error;
2462 };
2463
2464 typedef std::shared_ptr<FormDelegate> FormDelegateSP;
2465
2466 class FormWindowDelegate : public WindowDelegate {
2467 public:
FormWindowDelegate(FormDelegateSP & delegate_sp)2468 FormWindowDelegate(FormDelegateSP &delegate_sp)
2469 : m_delegate_sp(delegate_sp), m_selection_index(0),
2470 m_first_visible_line(0) {
2471 assert(m_delegate_sp->GetNumberOfActions() > 0);
2472 if (m_delegate_sp->GetNumberOfFields() > 0)
2473 m_selection_type = SelectionType::Field;
2474 else
2475 m_selection_type = SelectionType::Action;
2476 }
2477
2478 // Signify which element is selected. If a field or an action is selected,
2479 // then m_selection_index signifies the particular field or action that is
2480 // selected.
2481 enum class SelectionType { Field, Action };
2482
2483 // A form window is padded by one character from all sides. First, if an error
2484 // message exists, it is drawn followed by a separator. Then one or more
2485 // fields are drawn. Finally, all available actions are drawn on a single
2486 // line.
2487 //
2488 // ___<Form Name>_________________________________________________
2489 // | |
2490 // | - Error message if it exists. |
2491 // |-------------------------------------------------------------|
2492 // | Form elements here. |
2493 // | Form actions here. |
2494 // | |
2495 // |______________________________________[Press Esc to cancel]__|
2496 //
2497
2498 // One line for the error and another for the horizontal line.
GetErrorHeight()2499 int GetErrorHeight() {
2500 if (m_delegate_sp->HasError())
2501 return 2;
2502 return 0;
2503 }
2504
2505 // Actions span a single line.
GetActionsHeight()2506 int GetActionsHeight() {
2507 if (m_delegate_sp->GetNumberOfActions() > 0)
2508 return 1;
2509 return 0;
2510 }
2511
2512 // Get the total number of needed lines to draw the contents.
GetContentHeight()2513 int GetContentHeight() {
2514 int height = 0;
2515 height += GetErrorHeight();
2516 for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2517 if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2518 continue;
2519 height += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2520 }
2521 height += GetActionsHeight();
2522 return height;
2523 }
2524
GetScrollContext()2525 ScrollContext GetScrollContext() {
2526 if (m_selection_type == SelectionType::Action)
2527 return ScrollContext(GetContentHeight() - 1);
2528
2529 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2530 ScrollContext context = field->FieldDelegateGetScrollContext();
2531
2532 int offset = GetErrorHeight();
2533 for (int i = 0; i < m_selection_index; i++) {
2534 if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2535 continue;
2536 offset += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2537 }
2538 context.Offset(offset);
2539
2540 // If the context is touching the error, include the error in the context as
2541 // well.
2542 if (context.start == GetErrorHeight())
2543 context.start = 0;
2544
2545 return context;
2546 }
2547
UpdateScrolling(Surface & surface)2548 void UpdateScrolling(Surface &surface) {
2549 ScrollContext context = GetScrollContext();
2550 int content_height = GetContentHeight();
2551 int surface_height = surface.GetHeight();
2552 int visible_height = std::min(content_height, surface_height);
2553 int last_visible_line = m_first_visible_line + visible_height - 1;
2554
2555 // If the last visible line is bigger than the content, then it is invalid
2556 // and needs to be set to the last line in the content. This can happen when
2557 // a field has shrunk in height.
2558 if (last_visible_line > content_height - 1) {
2559 m_first_visible_line = content_height - visible_height;
2560 }
2561
2562 if (context.start < m_first_visible_line) {
2563 m_first_visible_line = context.start;
2564 return;
2565 }
2566
2567 if (context.end > last_visible_line) {
2568 m_first_visible_line = context.end - visible_height + 1;
2569 }
2570 }
2571
DrawError(Surface & surface)2572 void DrawError(Surface &surface) {
2573 if (!m_delegate_sp->HasError())
2574 return;
2575 surface.MoveCursor(0, 0);
2576 surface.AttributeOn(COLOR_PAIR(RedOnBlack));
2577 surface.PutChar(ACS_DIAMOND);
2578 surface.PutChar(' ');
2579 surface.PutCStringTruncated(1, m_delegate_sp->GetError().c_str());
2580 surface.AttributeOff(COLOR_PAIR(RedOnBlack));
2581
2582 surface.MoveCursor(0, 1);
2583 surface.HorizontalLine(surface.GetWidth());
2584 }
2585
DrawFields(Surface & surface)2586 void DrawFields(Surface &surface) {
2587 int line = 0;
2588 int width = surface.GetWidth();
2589 bool a_field_is_selected = m_selection_type == SelectionType::Field;
2590 for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2591 FieldDelegate *field = m_delegate_sp->GetField(i);
2592 if (!field->FieldDelegateIsVisible())
2593 continue;
2594 bool is_field_selected = a_field_is_selected && m_selection_index == i;
2595 int height = field->FieldDelegateGetHeight();
2596 Rect bounds = Rect(Point(0, line), Size(width, height));
2597 Surface field_surface = surface.SubSurface(bounds);
2598 field->FieldDelegateDraw(field_surface, is_field_selected);
2599 line += height;
2600 }
2601 }
2602
DrawActions(Surface & surface)2603 void DrawActions(Surface &surface) {
2604 int number_of_actions = m_delegate_sp->GetNumberOfActions();
2605 int width = surface.GetWidth() / number_of_actions;
2606 bool an_action_is_selected = m_selection_type == SelectionType::Action;
2607 int x = 0;
2608 for (int i = 0; i < number_of_actions; i++) {
2609 bool is_action_selected = an_action_is_selected && m_selection_index == i;
2610 FormAction &action = m_delegate_sp->GetAction(i);
2611 Rect bounds = Rect(Point(x, 0), Size(width, 1));
2612 Surface action_surface = surface.SubSurface(bounds);
2613 action.Draw(action_surface, is_action_selected);
2614 x += width;
2615 }
2616 }
2617
DrawElements(Surface & surface)2618 void DrawElements(Surface &surface) {
2619 Rect frame = surface.GetFrame();
2620 Rect fields_bounds, actions_bounds;
2621 frame.HorizontalSplit(surface.GetHeight() - GetActionsHeight(),
2622 fields_bounds, actions_bounds);
2623 Surface fields_surface = surface.SubSurface(fields_bounds);
2624 Surface actions_surface = surface.SubSurface(actions_bounds);
2625
2626 DrawFields(fields_surface);
2627 DrawActions(actions_surface);
2628 }
2629
2630 // Contents are first drawn on a pad. Then a subset of that pad is copied to
2631 // the derived window starting at the first visible line. This essentially
2632 // provides scrolling functionality.
DrawContent(Surface & surface)2633 void DrawContent(Surface &surface) {
2634 UpdateScrolling(surface);
2635
2636 int width = surface.GetWidth();
2637 int height = GetContentHeight();
2638 Pad pad = Pad(Size(width, height));
2639
2640 Rect frame = pad.GetFrame();
2641 Rect error_bounds, elements_bounds;
2642 frame.HorizontalSplit(GetErrorHeight(), error_bounds, elements_bounds);
2643 Surface error_surface = pad.SubSurface(error_bounds);
2644 Surface elements_surface = pad.SubSurface(elements_bounds);
2645
2646 DrawError(error_surface);
2647 DrawElements(elements_surface);
2648
2649 int copy_height = std::min(surface.GetHeight(), pad.GetHeight());
2650 pad.CopyToSurface(surface, Point(0, m_first_visible_line), Point(),
2651 Size(width, copy_height));
2652 }
2653
DrawSubmitHint(Surface & surface,bool is_active)2654 void DrawSubmitHint(Surface &surface, bool is_active) {
2655 surface.MoveCursor(2, surface.GetHeight() - 1);
2656 if (is_active)
2657 surface.AttributeOn(A_BOLD | COLOR_PAIR(BlackOnWhite));
2658 surface.Printf("[Press Alt+Enter to %s]",
2659 m_delegate_sp->GetAction(0).GetLabel().c_str());
2660 if (is_active)
2661 surface.AttributeOff(A_BOLD | COLOR_PAIR(BlackOnWhite));
2662 }
2663
WindowDelegateDraw(Window & window,bool force)2664 bool WindowDelegateDraw(Window &window, bool force) override {
2665 m_delegate_sp->UpdateFieldsVisibility();
2666
2667 window.Erase();
2668
2669 window.DrawTitleBox(m_delegate_sp->GetName().c_str(),
2670 "Press Esc to Cancel");
2671 DrawSubmitHint(window, window.IsActive());
2672
2673 Rect content_bounds = window.GetFrame();
2674 content_bounds.Inset(2, 2);
2675 Surface content_surface = window.SubSurface(content_bounds);
2676
2677 DrawContent(content_surface);
2678 return true;
2679 }
2680
SkipNextHiddenFields()2681 void SkipNextHiddenFields() {
2682 while (true) {
2683 if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2684 return;
2685
2686 if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2687 m_selection_type = SelectionType::Action;
2688 m_selection_index = 0;
2689 return;
2690 }
2691
2692 m_selection_index++;
2693 }
2694 }
2695
SelectNext(int key)2696 HandleCharResult SelectNext(int key) {
2697 if (m_selection_type == SelectionType::Action) {
2698 if (m_selection_index < m_delegate_sp->GetNumberOfActions() - 1) {
2699 m_selection_index++;
2700 return eKeyHandled;
2701 }
2702
2703 m_selection_index = 0;
2704 m_selection_type = SelectionType::Field;
2705 SkipNextHiddenFields();
2706 if (m_selection_type == SelectionType::Field) {
2707 FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2708 next_field->FieldDelegateSelectFirstElement();
2709 }
2710 return eKeyHandled;
2711 }
2712
2713 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2714 if (!field->FieldDelegateOnLastOrOnlyElement()) {
2715 return field->FieldDelegateHandleChar(key);
2716 }
2717
2718 field->FieldDelegateExitCallback();
2719
2720 if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2721 m_selection_type = SelectionType::Action;
2722 m_selection_index = 0;
2723 return eKeyHandled;
2724 }
2725
2726 m_selection_index++;
2727 SkipNextHiddenFields();
2728
2729 if (m_selection_type == SelectionType::Field) {
2730 FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2731 next_field->FieldDelegateSelectFirstElement();
2732 }
2733
2734 return eKeyHandled;
2735 }
2736
SkipPreviousHiddenFields()2737 void SkipPreviousHiddenFields() {
2738 while (true) {
2739 if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2740 return;
2741
2742 if (m_selection_index == 0) {
2743 m_selection_type = SelectionType::Action;
2744 m_selection_index = 0;
2745 return;
2746 }
2747
2748 m_selection_index--;
2749 }
2750 }
2751
SelectPrevious(int key)2752 HandleCharResult SelectPrevious(int key) {
2753 if (m_selection_type == SelectionType::Action) {
2754 if (m_selection_index > 0) {
2755 m_selection_index--;
2756 return eKeyHandled;
2757 }
2758 m_selection_index = m_delegate_sp->GetNumberOfFields() - 1;
2759 m_selection_type = SelectionType::Field;
2760 SkipPreviousHiddenFields();
2761 if (m_selection_type == SelectionType::Field) {
2762 FieldDelegate *previous_field =
2763 m_delegate_sp->GetField(m_selection_index);
2764 previous_field->FieldDelegateSelectLastElement();
2765 }
2766 return eKeyHandled;
2767 }
2768
2769 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2770 if (!field->FieldDelegateOnFirstOrOnlyElement()) {
2771 return field->FieldDelegateHandleChar(key);
2772 }
2773
2774 field->FieldDelegateExitCallback();
2775
2776 if (m_selection_index == 0) {
2777 m_selection_type = SelectionType::Action;
2778 m_selection_index = m_delegate_sp->GetNumberOfActions() - 1;
2779 return eKeyHandled;
2780 }
2781
2782 m_selection_index--;
2783 SkipPreviousHiddenFields();
2784
2785 if (m_selection_type == SelectionType::Field) {
2786 FieldDelegate *previous_field =
2787 m_delegate_sp->GetField(m_selection_index);
2788 previous_field->FieldDelegateSelectLastElement();
2789 }
2790
2791 return eKeyHandled;
2792 }
2793
ExecuteAction(Window & window,int index)2794 void ExecuteAction(Window &window, int index) {
2795 FormAction &action = m_delegate_sp->GetAction(index);
2796 action.Execute(window);
2797 if (m_delegate_sp->HasError()) {
2798 m_first_visible_line = 0;
2799 m_selection_index = 0;
2800 m_selection_type = SelectionType::Field;
2801 }
2802 }
2803
2804 // Always return eKeyHandled to absorb all events since forms are always
2805 // added as pop-ups that should take full control until canceled or submitted.
WindowDelegateHandleChar(Window & window,int key)2806 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
2807 switch (key) {
2808 case '\r':
2809 case '\n':
2810 case KEY_ENTER:
2811 if (m_selection_type == SelectionType::Action) {
2812 ExecuteAction(window, m_selection_index);
2813 return eKeyHandled;
2814 }
2815 break;
2816 case KEY_ALT_ENTER:
2817 ExecuteAction(window, 0);
2818 return eKeyHandled;
2819 case '\t':
2820 SelectNext(key);
2821 return eKeyHandled;
2822 case KEY_SHIFT_TAB:
2823 SelectPrevious(key);
2824 return eKeyHandled;
2825 case KEY_ESCAPE:
2826 window.GetParent()->RemoveSubWindow(&window);
2827 return eKeyHandled;
2828 default:
2829 break;
2830 }
2831
2832 // If the key wasn't handled and one of the fields is selected, pass the key
2833 // to that field.
2834 if (m_selection_type == SelectionType::Field) {
2835 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2836 if (field->FieldDelegateHandleChar(key) == eKeyHandled)
2837 return eKeyHandled;
2838 }
2839
2840 // If the key wasn't handled by the possibly selected field, handle some
2841 // extra keys for navigation.
2842 switch (key) {
2843 case KEY_DOWN:
2844 SelectNext(key);
2845 return eKeyHandled;
2846 case KEY_UP:
2847 SelectPrevious(key);
2848 return eKeyHandled;
2849 default:
2850 break;
2851 }
2852
2853 return eKeyHandled;
2854 }
2855
2856 protected:
2857 FormDelegateSP m_delegate_sp;
2858 // The index of the currently selected SelectionType.
2859 int m_selection_index;
2860 // See SelectionType class enum.
2861 SelectionType m_selection_type;
2862 // The first visible line from the pad.
2863 int m_first_visible_line;
2864 };
2865
2866 ///////////////////////////
2867 // Form Delegate Instances
2868 ///////////////////////////
2869
2870 class DetachOrKillProcessFormDelegate : public FormDelegate {
2871 public:
DetachOrKillProcessFormDelegate(Process * process)2872 DetachOrKillProcessFormDelegate(Process *process) : m_process(process) {
2873 SetError("There is a running process, either detach or kill it.");
2874
2875 m_keep_stopped_field =
2876 AddBooleanField("Keep process stopped when detaching.", false);
2877
2878 AddAction("Detach", [this](Window &window) { Detach(window); });
2879 AddAction("Kill", [this](Window &window) { Kill(window); });
2880 }
2881
GetName()2882 std::string GetName() override { return "Detach/Kill Process"; }
2883
Kill(Window & window)2884 void Kill(Window &window) {
2885 Status destroy_status(m_process->Destroy(false));
2886 if (destroy_status.Fail()) {
2887 SetError("Failed to kill process.");
2888 return;
2889 }
2890 window.GetParent()->RemoveSubWindow(&window);
2891 }
2892
Detach(Window & window)2893 void Detach(Window &window) {
2894 Status detach_status(m_process->Detach(m_keep_stopped_field->GetBoolean()));
2895 if (detach_status.Fail()) {
2896 SetError("Failed to detach from process.");
2897 return;
2898 }
2899 window.GetParent()->RemoveSubWindow(&window);
2900 }
2901
2902 protected:
2903 Process *m_process;
2904 BooleanFieldDelegate *m_keep_stopped_field;
2905 };
2906
2907 class ProcessAttachFormDelegate : public FormDelegate {
2908 public:
ProcessAttachFormDelegate(Debugger & debugger,WindowSP main_window_sp)2909 ProcessAttachFormDelegate(Debugger &debugger, WindowSP main_window_sp)
2910 : m_debugger(debugger), m_main_window_sp(main_window_sp) {
2911 std::vector<std::string> types;
2912 types.push_back(std::string("Name"));
2913 types.push_back(std::string("PID"));
2914 m_type_field = AddChoicesField("Attach By", 2, types);
2915 m_pid_field = AddIntegerField("PID", 0, true);
2916 m_name_field =
2917 AddTextField("Process Name", GetDefaultProcessName().c_str(), true);
2918 m_continue_field = AddBooleanField("Continue once attached.", false);
2919 m_wait_for_field = AddBooleanField("Wait for process to launch.", false);
2920 m_include_existing_field =
2921 AddBooleanField("Include existing processes.", false);
2922 m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
2923 m_plugin_field = AddProcessPluginField();
2924
2925 AddAction("Attach", [this](Window &window) { Attach(window); });
2926 }
2927
GetName()2928 std::string GetName() override { return "Attach Process"; }
2929
UpdateFieldsVisibility()2930 void UpdateFieldsVisibility() override {
2931 if (m_type_field->GetChoiceContent() == "Name") {
2932 m_pid_field->FieldDelegateHide();
2933 m_name_field->FieldDelegateShow();
2934 m_wait_for_field->FieldDelegateShow();
2935 if (m_wait_for_field->GetBoolean())
2936 m_include_existing_field->FieldDelegateShow();
2937 else
2938 m_include_existing_field->FieldDelegateHide();
2939 } else {
2940 m_pid_field->FieldDelegateShow();
2941 m_name_field->FieldDelegateHide();
2942 m_wait_for_field->FieldDelegateHide();
2943 m_include_existing_field->FieldDelegateHide();
2944 }
2945 if (m_show_advanced_field->GetBoolean())
2946 m_plugin_field->FieldDelegateShow();
2947 else
2948 m_plugin_field->FieldDelegateHide();
2949 }
2950
2951 // Get the basename of the target's main executable if available, empty string
2952 // otherwise.
GetDefaultProcessName()2953 std::string GetDefaultProcessName() {
2954 Target *target = m_debugger.GetSelectedTarget().get();
2955 if (target == nullptr)
2956 return "";
2957
2958 ModuleSP module_sp = target->GetExecutableModule();
2959 if (!module_sp->IsExecutable())
2960 return "";
2961
2962 return module_sp->GetFileSpec().GetFilename().AsCString();
2963 }
2964
StopRunningProcess()2965 bool StopRunningProcess() {
2966 ExecutionContext exe_ctx =
2967 m_debugger.GetCommandInterpreter().GetExecutionContext();
2968
2969 if (!exe_ctx.HasProcessScope())
2970 return false;
2971
2972 Process *process = exe_ctx.GetProcessPtr();
2973 if (!(process && process->IsAlive()))
2974 return false;
2975
2976 FormDelegateSP form_delegate_sp =
2977 FormDelegateSP(new DetachOrKillProcessFormDelegate(process));
2978 Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);
2979 WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(
2980 form_delegate_sp->GetName().c_str(), bounds, true);
2981 WindowDelegateSP window_delegate_sp =
2982 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
2983 form_window_sp->SetDelegate(window_delegate_sp);
2984
2985 return true;
2986 }
2987
GetTarget()2988 Target *GetTarget() {
2989 Target *target = m_debugger.GetSelectedTarget().get();
2990
2991 if (target != nullptr)
2992 return target;
2993
2994 TargetSP new_target_sp;
2995 m_debugger.GetTargetList().CreateTarget(
2996 m_debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp);
2997
2998 target = new_target_sp.get();
2999
3000 if (target == nullptr)
3001 SetError("Failed to create target.");
3002
3003 m_debugger.GetTargetList().SetSelectedTarget(new_target_sp);
3004
3005 return target;
3006 }
3007
GetAttachInfo()3008 ProcessAttachInfo GetAttachInfo() {
3009 ProcessAttachInfo attach_info;
3010 attach_info.SetContinueOnceAttached(m_continue_field->GetBoolean());
3011 if (m_type_field->GetChoiceContent() == "Name") {
3012 attach_info.GetExecutableFile().SetFile(m_name_field->GetText(),
3013 FileSpec::Style::native);
3014 attach_info.SetWaitForLaunch(m_wait_for_field->GetBoolean());
3015 if (m_wait_for_field->GetBoolean())
3016 attach_info.SetIgnoreExisting(!m_include_existing_field->GetBoolean());
3017 } else {
3018 attach_info.SetProcessID(m_pid_field->GetInteger());
3019 }
3020 attach_info.SetProcessPluginName(m_plugin_field->GetPluginName());
3021
3022 return attach_info;
3023 }
3024
Attach(Window & window)3025 void Attach(Window &window) {
3026 ClearError();
3027
3028 bool all_fields_are_valid = CheckFieldsValidity();
3029 if (!all_fields_are_valid)
3030 return;
3031
3032 bool process_is_running = StopRunningProcess();
3033 if (process_is_running)
3034 return;
3035
3036 Target *target = GetTarget();
3037 if (HasError())
3038 return;
3039
3040 StreamString stream;
3041 ProcessAttachInfo attach_info = GetAttachInfo();
3042 Status status = target->Attach(attach_info, &stream);
3043
3044 if (status.Fail()) {
3045 SetError(status.AsCString());
3046 return;
3047 }
3048
3049 ProcessSP process_sp(target->GetProcessSP());
3050 if (!process_sp) {
3051 SetError("Attached sucessfully but target has no process.");
3052 return;
3053 }
3054
3055 if (attach_info.GetContinueOnceAttached())
3056 process_sp->Resume();
3057
3058 window.GetParent()->RemoveSubWindow(&window);
3059 }
3060
3061 protected:
3062 Debugger &m_debugger;
3063 WindowSP m_main_window_sp;
3064
3065 ChoicesFieldDelegate *m_type_field;
3066 IntegerFieldDelegate *m_pid_field;
3067 TextFieldDelegate *m_name_field;
3068 BooleanFieldDelegate *m_continue_field;
3069 BooleanFieldDelegate *m_wait_for_field;
3070 BooleanFieldDelegate *m_include_existing_field;
3071 BooleanFieldDelegate *m_show_advanced_field;
3072 ProcessPluginFieldDelegate *m_plugin_field;
3073 };
3074
3075 class TargetCreateFormDelegate : public FormDelegate {
3076 public:
TargetCreateFormDelegate(Debugger & debugger)3077 TargetCreateFormDelegate(Debugger &debugger) : m_debugger(debugger) {
3078 m_executable_field = AddFileField("Executable", "", /*need_to_exist=*/true,
3079 /*required=*/true);
3080 m_core_file_field = AddFileField("Core File", "", /*need_to_exist=*/true,
3081 /*required=*/false);
3082 m_symbol_file_field = AddFileField(
3083 "Symbol File", "", /*need_to_exist=*/true, /*required=*/false);
3084 m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
3085 m_remote_file_field = AddFileField(
3086 "Remote File", "", /*need_to_exist=*/false, /*required=*/false);
3087 m_arch_field = AddArchField("Architecture", "", /*required=*/false);
3088 m_platform_field = AddPlatformPluginField(debugger);
3089 m_load_dependent_files_field =
3090 AddChoicesField("Load Dependents", 3, GetLoadDependentFilesChoices());
3091
3092 AddAction("Create", [this](Window &window) { CreateTarget(window); });
3093 }
3094
GetName()3095 std::string GetName() override { return "Create Target"; }
3096
UpdateFieldsVisibility()3097 void UpdateFieldsVisibility() override {
3098 if (m_show_advanced_field->GetBoolean()) {
3099 m_remote_file_field->FieldDelegateShow();
3100 m_arch_field->FieldDelegateShow();
3101 m_platform_field->FieldDelegateShow();
3102 m_load_dependent_files_field->FieldDelegateShow();
3103 } else {
3104 m_remote_file_field->FieldDelegateHide();
3105 m_arch_field->FieldDelegateHide();
3106 m_platform_field->FieldDelegateHide();
3107 m_load_dependent_files_field->FieldDelegateHide();
3108 }
3109 }
3110
3111 static constexpr const char *kLoadDependentFilesNo = "No";
3112 static constexpr const char *kLoadDependentFilesYes = "Yes";
3113 static constexpr const char *kLoadDependentFilesExecOnly = "Executable only";
3114
GetLoadDependentFilesChoices()3115 std::vector<std::string> GetLoadDependentFilesChoices() {
3116 std::vector<std::string> load_depentents_options;
3117 load_depentents_options.push_back(kLoadDependentFilesExecOnly);
3118 load_depentents_options.push_back(kLoadDependentFilesYes);
3119 load_depentents_options.push_back(kLoadDependentFilesNo);
3120 return load_depentents_options;
3121 }
3122
GetLoadDependentFiles()3123 LoadDependentFiles GetLoadDependentFiles() {
3124 std::string choice = m_load_dependent_files_field->GetChoiceContent();
3125 if (choice == kLoadDependentFilesNo)
3126 return eLoadDependentsNo;
3127 if (choice == kLoadDependentFilesYes)
3128 return eLoadDependentsYes;
3129 return eLoadDependentsDefault;
3130 }
3131
GetPlatformOptions()3132 OptionGroupPlatform GetPlatformOptions() {
3133 OptionGroupPlatform platform_options(false);
3134 platform_options.SetPlatformName(m_platform_field->GetPluginName().c_str());
3135 return platform_options;
3136 }
3137
GetTarget()3138 TargetSP GetTarget() {
3139 OptionGroupPlatform platform_options = GetPlatformOptions();
3140 TargetSP target_sp;
3141 Status status = m_debugger.GetTargetList().CreateTarget(
3142 m_debugger, m_executable_field->GetPath(),
3143 m_arch_field->GetArchString(), GetLoadDependentFiles(),
3144 &platform_options, target_sp);
3145
3146 if (status.Fail()) {
3147 SetError(status.AsCString());
3148 return nullptr;
3149 }
3150
3151 m_debugger.GetTargetList().SetSelectedTarget(target_sp);
3152
3153 return target_sp;
3154 }
3155
SetSymbolFile(TargetSP target_sp)3156 void SetSymbolFile(TargetSP target_sp) {
3157 if (!m_symbol_file_field->IsSpecified())
3158 return;
3159
3160 ModuleSP module_sp(target_sp->GetExecutableModule());
3161 if (!module_sp)
3162 return;
3163
3164 module_sp->SetSymbolFileFileSpec(
3165 m_symbol_file_field->GetResolvedFileSpec());
3166 }
3167
SetCoreFile(TargetSP target_sp)3168 void SetCoreFile(TargetSP target_sp) {
3169 if (!m_core_file_field->IsSpecified())
3170 return;
3171
3172 FileSpec core_file_spec = m_core_file_field->GetResolvedFileSpec();
3173
3174 FileSpec core_file_directory_spec;
3175 core_file_directory_spec.GetDirectory() = core_file_spec.GetDirectory();
3176 target_sp->AppendExecutableSearchPaths(core_file_directory_spec);
3177
3178 ProcessSP process_sp(target_sp->CreateProcess(
3179 m_debugger.GetListener(), llvm::StringRef(), &core_file_spec, false));
3180
3181 if (!process_sp) {
3182 SetError("Unable to find process plug-in for core file!");
3183 return;
3184 }
3185
3186 Status status = process_sp->LoadCore();
3187 if (status.Fail()) {
3188 SetError("Can't find plug-in for core file!");
3189 return;
3190 }
3191 }
3192
SetRemoteFile(TargetSP target_sp)3193 void SetRemoteFile(TargetSP target_sp) {
3194 if (!m_remote_file_field->IsSpecified())
3195 return;
3196
3197 ModuleSP module_sp(target_sp->GetExecutableModule());
3198 if (!module_sp)
3199 return;
3200
3201 FileSpec remote_file_spec = m_remote_file_field->GetFileSpec();
3202 module_sp->SetPlatformFileSpec(remote_file_spec);
3203 }
3204
RemoveTarget(TargetSP target_sp)3205 void RemoveTarget(TargetSP target_sp) {
3206 m_debugger.GetTargetList().DeleteTarget(target_sp);
3207 }
3208
CreateTarget(Window & window)3209 void CreateTarget(Window &window) {
3210 ClearError();
3211
3212 bool all_fields_are_valid = CheckFieldsValidity();
3213 if (!all_fields_are_valid)
3214 return;
3215
3216 TargetSP target_sp = GetTarget();
3217 if (HasError())
3218 return;
3219
3220 SetSymbolFile(target_sp);
3221 if (HasError()) {
3222 RemoveTarget(target_sp);
3223 return;
3224 }
3225
3226 SetCoreFile(target_sp);
3227 if (HasError()) {
3228 RemoveTarget(target_sp);
3229 return;
3230 }
3231
3232 SetRemoteFile(target_sp);
3233 if (HasError()) {
3234 RemoveTarget(target_sp);
3235 return;
3236 }
3237
3238 window.GetParent()->RemoveSubWindow(&window);
3239 }
3240
3241 protected:
3242 Debugger &m_debugger;
3243
3244 FileFieldDelegate *m_executable_field;
3245 FileFieldDelegate *m_core_file_field;
3246 FileFieldDelegate *m_symbol_file_field;
3247 BooleanFieldDelegate *m_show_advanced_field;
3248 FileFieldDelegate *m_remote_file_field;
3249 ArchFieldDelegate *m_arch_field;
3250 PlatformPluginFieldDelegate *m_platform_field;
3251 ChoicesFieldDelegate *m_load_dependent_files_field;
3252 };
3253
3254 class ProcessLaunchFormDelegate : public FormDelegate {
3255 public:
ProcessLaunchFormDelegate(Debugger & debugger,WindowSP main_window_sp)3256 ProcessLaunchFormDelegate(Debugger &debugger, WindowSP main_window_sp)
3257 : m_debugger(debugger), m_main_window_sp(main_window_sp) {
3258
3259 m_arguments_field = AddArgumentsField();
3260 SetArgumentsFieldDefaultValue();
3261 m_target_environment_field =
3262 AddEnvironmentVariableListField("Target Environment Variables");
3263 SetTargetEnvironmentFieldDefaultValue();
3264 m_working_directory_field = AddDirectoryField(
3265 "Working Directory", GetDefaultWorkingDirectory().c_str(), true, false);
3266
3267 m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
3268
3269 m_stop_at_entry_field = AddBooleanField("Stop at entry point.", false);
3270 m_detach_on_error_field =
3271 AddBooleanField("Detach on error.", GetDefaultDetachOnError());
3272 m_disable_aslr_field =
3273 AddBooleanField("Disable ASLR", GetDefaultDisableASLR());
3274 m_plugin_field = AddProcessPluginField();
3275 m_arch_field = AddArchField("Architecture", "", false);
3276 m_shell_field = AddFileField("Shell", "", true, false);
3277 m_expand_shell_arguments_field =
3278 AddBooleanField("Expand shell arguments.", false);
3279
3280 m_disable_standard_io_field =
3281 AddBooleanField("Disable Standard IO", GetDefaultDisableStandardIO());
3282 m_standard_output_field =
3283 AddFileField("Standard Output File", "", /*need_to_exist=*/false,
3284 /*required=*/false);
3285 m_standard_error_field =
3286 AddFileField("Standard Error File", "", /*need_to_exist=*/false,
3287 /*required=*/false);
3288 m_standard_input_field =
3289 AddFileField("Standard Input File", "", /*need_to_exist=*/false,
3290 /*required=*/false);
3291
3292 m_show_inherited_environment_field =
3293 AddBooleanField("Show inherited environment variables.", false);
3294 m_inherited_environment_field =
3295 AddEnvironmentVariableListField("Inherited Environment Variables");
3296 SetInheritedEnvironmentFieldDefaultValue();
3297
3298 AddAction("Launch", [this](Window &window) { Launch(window); });
3299 }
3300
GetName()3301 std::string GetName() override { return "Launch Process"; }
3302
UpdateFieldsVisibility()3303 void UpdateFieldsVisibility() override {
3304 if (m_show_advanced_field->GetBoolean()) {
3305 m_stop_at_entry_field->FieldDelegateShow();
3306 m_detach_on_error_field->FieldDelegateShow();
3307 m_disable_aslr_field->FieldDelegateShow();
3308 m_plugin_field->FieldDelegateShow();
3309 m_arch_field->FieldDelegateShow();
3310 m_shell_field->FieldDelegateShow();
3311 m_expand_shell_arguments_field->FieldDelegateShow();
3312 m_disable_standard_io_field->FieldDelegateShow();
3313 if (m_disable_standard_io_field->GetBoolean()) {
3314 m_standard_input_field->FieldDelegateHide();
3315 m_standard_output_field->FieldDelegateHide();
3316 m_standard_error_field->FieldDelegateHide();
3317 } else {
3318 m_standard_input_field->FieldDelegateShow();
3319 m_standard_output_field->FieldDelegateShow();
3320 m_standard_error_field->FieldDelegateShow();
3321 }
3322 m_show_inherited_environment_field->FieldDelegateShow();
3323 if (m_show_inherited_environment_field->GetBoolean())
3324 m_inherited_environment_field->FieldDelegateShow();
3325 else
3326 m_inherited_environment_field->FieldDelegateHide();
3327 } else {
3328 m_stop_at_entry_field->FieldDelegateHide();
3329 m_detach_on_error_field->FieldDelegateHide();
3330 m_disable_aslr_field->FieldDelegateHide();
3331 m_plugin_field->FieldDelegateHide();
3332 m_arch_field->FieldDelegateHide();
3333 m_shell_field->FieldDelegateHide();
3334 m_expand_shell_arguments_field->FieldDelegateHide();
3335 m_disable_standard_io_field->FieldDelegateHide();
3336 m_standard_input_field->FieldDelegateHide();
3337 m_standard_output_field->FieldDelegateHide();
3338 m_standard_error_field->FieldDelegateHide();
3339 m_show_inherited_environment_field->FieldDelegateHide();
3340 m_inherited_environment_field->FieldDelegateHide();
3341 }
3342 }
3343
3344 // Methods for setting the default value of the fields.
3345
SetArgumentsFieldDefaultValue()3346 void SetArgumentsFieldDefaultValue() {
3347 TargetSP target = m_debugger.GetSelectedTarget();
3348 if (target == nullptr)
3349 return;
3350
3351 const Args &target_arguments =
3352 target->GetProcessLaunchInfo().GetArguments();
3353 m_arguments_field->AddArguments(target_arguments);
3354 }
3355
SetTargetEnvironmentFieldDefaultValue()3356 void SetTargetEnvironmentFieldDefaultValue() {
3357 TargetSP target = m_debugger.GetSelectedTarget();
3358 if (target == nullptr)
3359 return;
3360
3361 const Environment &target_environment = target->GetTargetEnvironment();
3362 m_target_environment_field->AddEnvironmentVariables(target_environment);
3363 }
3364
SetInheritedEnvironmentFieldDefaultValue()3365 void SetInheritedEnvironmentFieldDefaultValue() {
3366 TargetSP target = m_debugger.GetSelectedTarget();
3367 if (target == nullptr)
3368 return;
3369
3370 const Environment &inherited_environment =
3371 target->GetInheritedEnvironment();
3372 m_inherited_environment_field->AddEnvironmentVariables(
3373 inherited_environment);
3374 }
3375
GetDefaultWorkingDirectory()3376 std::string GetDefaultWorkingDirectory() {
3377 TargetSP target = m_debugger.GetSelectedTarget();
3378 if (target == nullptr)
3379 return "";
3380
3381 PlatformSP platform = target->GetPlatform();
3382 return platform->GetWorkingDirectory().GetPath();
3383 }
3384
GetDefaultDisableASLR()3385 bool GetDefaultDisableASLR() {
3386 TargetSP target = m_debugger.GetSelectedTarget();
3387 if (target == nullptr)
3388 return false;
3389
3390 return target->GetDisableASLR();
3391 }
3392
GetDefaultDisableStandardIO()3393 bool GetDefaultDisableStandardIO() {
3394 TargetSP target = m_debugger.GetSelectedTarget();
3395 if (target == nullptr)
3396 return true;
3397
3398 return target->GetDisableSTDIO();
3399 }
3400
GetDefaultDetachOnError()3401 bool GetDefaultDetachOnError() {
3402 TargetSP target = m_debugger.GetSelectedTarget();
3403 if (target == nullptr)
3404 return true;
3405
3406 return target->GetDetachOnError();
3407 }
3408
3409 // Methods for getting the necessary information and setting them to the
3410 // ProcessLaunchInfo.
3411
GetExecutableSettings(ProcessLaunchInfo & launch_info)3412 void GetExecutableSettings(ProcessLaunchInfo &launch_info) {
3413 TargetSP target = m_debugger.GetSelectedTarget();
3414 ModuleSP executable_module = target->GetExecutableModule();
3415 llvm::StringRef target_settings_argv0 = target->GetArg0();
3416
3417 if (!target_settings_argv0.empty()) {
3418 launch_info.GetArguments().AppendArgument(target_settings_argv0);
3419 launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(),
3420 false);
3421 return;
3422 }
3423
3424 launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(),
3425 true);
3426 }
3427
GetArguments(ProcessLaunchInfo & launch_info)3428 void GetArguments(ProcessLaunchInfo &launch_info) {
3429 TargetSP target = m_debugger.GetSelectedTarget();
3430 Args arguments = m_arguments_field->GetArguments();
3431 launch_info.GetArguments().AppendArguments(arguments);
3432 }
3433
GetEnvironment(ProcessLaunchInfo & launch_info)3434 void GetEnvironment(ProcessLaunchInfo &launch_info) {
3435 Environment target_environment =
3436 m_target_environment_field->GetEnvironment();
3437 Environment inherited_environment =
3438 m_inherited_environment_field->GetEnvironment();
3439 launch_info.GetEnvironment().insert(target_environment.begin(),
3440 target_environment.end());
3441 launch_info.GetEnvironment().insert(inherited_environment.begin(),
3442 inherited_environment.end());
3443 }
3444
GetWorkingDirectory(ProcessLaunchInfo & launch_info)3445 void GetWorkingDirectory(ProcessLaunchInfo &launch_info) {
3446 if (m_working_directory_field->IsSpecified())
3447 launch_info.SetWorkingDirectory(
3448 m_working_directory_field->GetResolvedFileSpec());
3449 }
3450
GetStopAtEntry(ProcessLaunchInfo & launch_info)3451 void GetStopAtEntry(ProcessLaunchInfo &launch_info) {
3452 if (m_stop_at_entry_field->GetBoolean())
3453 launch_info.GetFlags().Set(eLaunchFlagStopAtEntry);
3454 else
3455 launch_info.GetFlags().Clear(eLaunchFlagStopAtEntry);
3456 }
3457
GetDetachOnError(ProcessLaunchInfo & launch_info)3458 void GetDetachOnError(ProcessLaunchInfo &launch_info) {
3459 if (m_detach_on_error_field->GetBoolean())
3460 launch_info.GetFlags().Set(eLaunchFlagDetachOnError);
3461 else
3462 launch_info.GetFlags().Clear(eLaunchFlagDetachOnError);
3463 }
3464
GetDisableASLR(ProcessLaunchInfo & launch_info)3465 void GetDisableASLR(ProcessLaunchInfo &launch_info) {
3466 if (m_disable_aslr_field->GetBoolean())
3467 launch_info.GetFlags().Set(eLaunchFlagDisableASLR);
3468 else
3469 launch_info.GetFlags().Clear(eLaunchFlagDisableASLR);
3470 }
3471
GetPlugin(ProcessLaunchInfo & launch_info)3472 void GetPlugin(ProcessLaunchInfo &launch_info) {
3473 launch_info.SetProcessPluginName(m_plugin_field->GetPluginName());
3474 }
3475
GetArch(ProcessLaunchInfo & launch_info)3476 void GetArch(ProcessLaunchInfo &launch_info) {
3477 if (!m_arch_field->IsSpecified())
3478 return;
3479
3480 TargetSP target_sp = m_debugger.GetSelectedTarget();
3481 PlatformSP platform_sp =
3482 target_sp ? target_sp->GetPlatform() : PlatformSP();
3483 launch_info.GetArchitecture() = Platform::GetAugmentedArchSpec(
3484 platform_sp.get(), m_arch_field->GetArchString());
3485 }
3486
GetShell(ProcessLaunchInfo & launch_info)3487 void GetShell(ProcessLaunchInfo &launch_info) {
3488 if (!m_shell_field->IsSpecified())
3489 return;
3490
3491 launch_info.SetShell(m_shell_field->GetResolvedFileSpec());
3492 launch_info.SetShellExpandArguments(
3493 m_expand_shell_arguments_field->GetBoolean());
3494 }
3495
GetStandardIO(ProcessLaunchInfo & launch_info)3496 void GetStandardIO(ProcessLaunchInfo &launch_info) {
3497 if (m_disable_standard_io_field->GetBoolean()) {
3498 launch_info.GetFlags().Set(eLaunchFlagDisableSTDIO);
3499 return;
3500 }
3501
3502 FileAction action;
3503 if (m_standard_input_field->IsSpecified()) {
3504 action.Open(STDIN_FILENO, m_standard_input_field->GetFileSpec(), true,
3505 false);
3506 launch_info.AppendFileAction(action);
3507 }
3508 if (m_standard_output_field->IsSpecified()) {
3509 action.Open(STDOUT_FILENO, m_standard_output_field->GetFileSpec(), false,
3510 true);
3511 launch_info.AppendFileAction(action);
3512 }
3513 if (m_standard_error_field->IsSpecified()) {
3514 action.Open(STDERR_FILENO, m_standard_error_field->GetFileSpec(), false,
3515 true);
3516 launch_info.AppendFileAction(action);
3517 }
3518 }
3519
GetInheritTCC(ProcessLaunchInfo & launch_info)3520 void GetInheritTCC(ProcessLaunchInfo &launch_info) {
3521 if (m_debugger.GetSelectedTarget()->GetInheritTCC())
3522 launch_info.GetFlags().Set(eLaunchFlagInheritTCCFromParent);
3523 }
3524
GetLaunchInfo()3525 ProcessLaunchInfo GetLaunchInfo() {
3526 ProcessLaunchInfo launch_info;
3527
3528 GetExecutableSettings(launch_info);
3529 GetArguments(launch_info);
3530 GetEnvironment(launch_info);
3531 GetWorkingDirectory(launch_info);
3532 GetStopAtEntry(launch_info);
3533 GetDetachOnError(launch_info);
3534 GetDisableASLR(launch_info);
3535 GetPlugin(launch_info);
3536 GetArch(launch_info);
3537 GetShell(launch_info);
3538 GetStandardIO(launch_info);
3539 GetInheritTCC(launch_info);
3540
3541 return launch_info;
3542 }
3543
StopRunningProcess()3544 bool StopRunningProcess() {
3545 ExecutionContext exe_ctx =
3546 m_debugger.GetCommandInterpreter().GetExecutionContext();
3547
3548 if (!exe_ctx.HasProcessScope())
3549 return false;
3550
3551 Process *process = exe_ctx.GetProcessPtr();
3552 if (!(process && process->IsAlive()))
3553 return false;
3554
3555 FormDelegateSP form_delegate_sp =
3556 FormDelegateSP(new DetachOrKillProcessFormDelegate(process));
3557 Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);
3558 WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(
3559 form_delegate_sp->GetName().c_str(), bounds, true);
3560 WindowDelegateSP window_delegate_sp =
3561 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
3562 form_window_sp->SetDelegate(window_delegate_sp);
3563
3564 return true;
3565 }
3566
GetTarget()3567 Target *GetTarget() {
3568 Target *target = m_debugger.GetSelectedTarget().get();
3569
3570 if (target == nullptr) {
3571 SetError("No target exists!");
3572 return nullptr;
3573 }
3574
3575 ModuleSP exe_module_sp = target->GetExecutableModule();
3576
3577 if (exe_module_sp == nullptr) {
3578 SetError("No executable in target!");
3579 return nullptr;
3580 }
3581
3582 return target;
3583 }
3584
Launch(Window & window)3585 void Launch(Window &window) {
3586 ClearError();
3587
3588 bool all_fields_are_valid = CheckFieldsValidity();
3589 if (!all_fields_are_valid)
3590 return;
3591
3592 bool process_is_running = StopRunningProcess();
3593 if (process_is_running)
3594 return;
3595
3596 Target *target = GetTarget();
3597 if (HasError())
3598 return;
3599
3600 StreamString stream;
3601 ProcessLaunchInfo launch_info = GetLaunchInfo();
3602 Status status = target->Launch(launch_info, &stream);
3603
3604 if (status.Fail()) {
3605 SetError(status.AsCString());
3606 return;
3607 }
3608
3609 ProcessSP process_sp(target->GetProcessSP());
3610 if (!process_sp) {
3611 SetError("Launched successfully but target has no process!");
3612 return;
3613 }
3614
3615 window.GetParent()->RemoveSubWindow(&window);
3616 }
3617
3618 protected:
3619 Debugger &m_debugger;
3620 WindowSP m_main_window_sp;
3621
3622 ArgumentsFieldDelegate *m_arguments_field;
3623 EnvironmentVariableListFieldDelegate *m_target_environment_field;
3624 DirectoryFieldDelegate *m_working_directory_field;
3625
3626 BooleanFieldDelegate *m_show_advanced_field;
3627
3628 BooleanFieldDelegate *m_stop_at_entry_field;
3629 BooleanFieldDelegate *m_detach_on_error_field;
3630 BooleanFieldDelegate *m_disable_aslr_field;
3631 ProcessPluginFieldDelegate *m_plugin_field;
3632 ArchFieldDelegate *m_arch_field;
3633 FileFieldDelegate *m_shell_field;
3634 BooleanFieldDelegate *m_expand_shell_arguments_field;
3635 BooleanFieldDelegate *m_disable_standard_io_field;
3636 FileFieldDelegate *m_standard_input_field;
3637 FileFieldDelegate *m_standard_output_field;
3638 FileFieldDelegate *m_standard_error_field;
3639
3640 BooleanFieldDelegate *m_show_inherited_environment_field;
3641 EnvironmentVariableListFieldDelegate *m_inherited_environment_field;
3642 };
3643
3644 ////////////
3645 // Searchers
3646 ////////////
3647
3648 class SearcherDelegate {
3649 public:
SearcherDelegate()3650 SearcherDelegate() {}
3651
3652 virtual ~SearcherDelegate() = default;
3653
3654 virtual int GetNumberOfMatches() = 0;
3655
3656 // Get the string that will be displayed for the match at the input index.
3657 virtual const std::string &GetMatchTextAtIndex(int index) = 0;
3658
3659 // Update the matches of the search. This is executed every time the text
3660 // field handles an event.
3661 virtual void UpdateMatches(const std::string &text) = 0;
3662
3663 // Execute the user callback given the index of some match. This is executed
3664 // once the user selects a match.
3665 virtual void ExecuteCallback(int match_index) = 0;
3666 };
3667
3668 typedef std::shared_ptr<SearcherDelegate> SearcherDelegateSP;
3669
3670 class SearcherWindowDelegate : public WindowDelegate {
3671 public:
SearcherWindowDelegate(SearcherDelegateSP & delegate_sp)3672 SearcherWindowDelegate(SearcherDelegateSP &delegate_sp)
3673 : m_delegate_sp(delegate_sp), m_text_field("Search", "", false),
3674 m_selected_match(0), m_first_visible_match(0) {
3675 ;
3676 }
3677
3678 // A completion window is padded by one character from all sides. A text field
3679 // is first drawn for inputting the searcher request, then a list of matches
3680 // are displayed in a scrollable list.
3681 //
3682 // ___<Searcher Window Name>____________________________
3683 // | |
3684 // | __[Search]_______________________________________ |
3685 // | | | |
3686 // | |_______________________________________________| |
3687 // | - Match 1. |
3688 // | - Match 2. |
3689 // | - ... |
3690 // | |
3691 // |____________________________[Press Esc to Cancel]__|
3692 //
3693
3694 // Get the index of the last visible match. Assuming at least one match
3695 // exists.
GetLastVisibleMatch(int height)3696 int GetLastVisibleMatch(int height) {
3697 int index = m_first_visible_match + height;
3698 return std::min(index, m_delegate_sp->GetNumberOfMatches()) - 1;
3699 }
3700
GetNumberOfVisibleMatches(int height)3701 int GetNumberOfVisibleMatches(int height) {
3702 return GetLastVisibleMatch(height) - m_first_visible_match + 1;
3703 }
3704
UpdateScrolling(Surface & surface)3705 void UpdateScrolling(Surface &surface) {
3706 if (m_selected_match < m_first_visible_match) {
3707 m_first_visible_match = m_selected_match;
3708 return;
3709 }
3710
3711 int height = surface.GetHeight();
3712 int last_visible_match = GetLastVisibleMatch(height);
3713 if (m_selected_match > last_visible_match) {
3714 m_first_visible_match = m_selected_match - height + 1;
3715 }
3716 }
3717
DrawMatches(Surface & surface)3718 void DrawMatches(Surface &surface) {
3719 if (m_delegate_sp->GetNumberOfMatches() == 0)
3720 return;
3721
3722 UpdateScrolling(surface);
3723
3724 int count = GetNumberOfVisibleMatches(surface.GetHeight());
3725 for (int i = 0; i < count; i++) {
3726 surface.MoveCursor(1, i);
3727 int current_match = m_first_visible_match + i;
3728 if (current_match == m_selected_match)
3729 surface.AttributeOn(A_REVERSE);
3730 surface.PutCString(
3731 m_delegate_sp->GetMatchTextAtIndex(current_match).c_str());
3732 if (current_match == m_selected_match)
3733 surface.AttributeOff(A_REVERSE);
3734 }
3735 }
3736
DrawContent(Surface & surface)3737 void DrawContent(Surface &surface) {
3738 Rect content_bounds = surface.GetFrame();
3739 Rect text_field_bounds, matchs_bounds;
3740 content_bounds.HorizontalSplit(m_text_field.FieldDelegateGetHeight(),
3741 text_field_bounds, matchs_bounds);
3742 Surface text_field_surface = surface.SubSurface(text_field_bounds);
3743 Surface matches_surface = surface.SubSurface(matchs_bounds);
3744
3745 m_text_field.FieldDelegateDraw(text_field_surface, true);
3746 DrawMatches(matches_surface);
3747 }
3748
WindowDelegateDraw(Window & window,bool force)3749 bool WindowDelegateDraw(Window &window, bool force) override {
3750 window.Erase();
3751
3752 window.DrawTitleBox(window.GetName(), "Press Esc to Cancel");
3753
3754 Rect content_bounds = window.GetFrame();
3755 content_bounds.Inset(2, 2);
3756 Surface content_surface = window.SubSurface(content_bounds);
3757
3758 DrawContent(content_surface);
3759 return true;
3760 }
3761
SelectNext()3762 void SelectNext() {
3763 if (m_selected_match != m_delegate_sp->GetNumberOfMatches() - 1)
3764 m_selected_match++;
3765 return;
3766 }
3767
SelectPrevious()3768 void SelectPrevious() {
3769 if (m_selected_match != 0)
3770 m_selected_match--;
3771 return;
3772 }
3773
ExecuteCallback(Window & window)3774 void ExecuteCallback(Window &window) {
3775 m_delegate_sp->ExecuteCallback(m_selected_match);
3776 window.GetParent()->RemoveSubWindow(&window);
3777 }
3778
UpdateMatches()3779 void UpdateMatches() {
3780 m_delegate_sp->UpdateMatches(m_text_field.GetText());
3781 m_selected_match = 0;
3782 }
3783
WindowDelegateHandleChar(Window & window,int key)3784 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
3785 switch (key) {
3786 case '\r':
3787 case '\n':
3788 case KEY_ENTER:
3789 ExecuteCallback(window);
3790 return eKeyHandled;
3791 case '\t':
3792 case KEY_DOWN:
3793 SelectNext();
3794 return eKeyHandled;
3795 case KEY_SHIFT_TAB:
3796 case KEY_UP:
3797 SelectPrevious();
3798 return eKeyHandled;
3799 case KEY_ESCAPE:
3800 window.GetParent()->RemoveSubWindow(&window);
3801 return eKeyHandled;
3802 default:
3803 break;
3804 }
3805
3806 if (m_text_field.FieldDelegateHandleChar(key) == eKeyHandled)
3807 UpdateMatches();
3808
3809 return eKeyHandled;
3810 }
3811
3812 protected:
3813 SearcherDelegateSP m_delegate_sp;
3814 TextFieldDelegate m_text_field;
3815 // The index of the currently selected match.
3816 int m_selected_match;
3817 // The index of the first visible match.
3818 int m_first_visible_match;
3819 };
3820
3821 //////////////////////////////
3822 // Searcher Delegate Instances
3823 //////////////////////////////
3824
3825 // This is a searcher delegate wrapper around CommandCompletions common
3826 // callbacks. The callbacks are only given the match string. The completion_mask
3827 // can be a combination of CommonCompletionTypes.
3828 class CommonCompletionSearcherDelegate : public SearcherDelegate {
3829 public:
3830 typedef std::function<void(const std::string &)> CallbackType;
3831
CommonCompletionSearcherDelegate(Debugger & debugger,uint32_t completion_mask,CallbackType callback)3832 CommonCompletionSearcherDelegate(Debugger &debugger, uint32_t completion_mask,
3833 CallbackType callback)
3834 : m_debugger(debugger), m_completion_mask(completion_mask),
3835 m_callback(callback) {}
3836
GetNumberOfMatches()3837 int GetNumberOfMatches() override { return m_matches.GetSize(); }
3838
GetMatchTextAtIndex(int index)3839 const std::string &GetMatchTextAtIndex(int index) override {
3840 return m_matches[index];
3841 }
3842
UpdateMatches(const std::string & text)3843 void UpdateMatches(const std::string &text) override {
3844 CompletionResult result;
3845 CompletionRequest request(text.c_str(), text.size(), result);
3846 CommandCompletions::InvokeCommonCompletionCallbacks(
3847 m_debugger.GetCommandInterpreter(), m_completion_mask, request,
3848 nullptr);
3849 result.GetMatches(m_matches);
3850 }
3851
ExecuteCallback(int match_index)3852 void ExecuteCallback(int match_index) override {
3853 m_callback(m_matches[match_index]);
3854 }
3855
3856 protected:
3857 Debugger &m_debugger;
3858 // A compound mask from CommonCompletionTypes.
3859 uint32_t m_completion_mask;
3860 // A callback to execute once the user selects a match. The match is passed to
3861 // the callback as a string.
3862 CallbackType m_callback;
3863 StringList m_matches;
3864 };
3865
3866 ////////
3867 // Menus
3868 ////////
3869
3870 class MenuDelegate {
3871 public:
3872 virtual ~MenuDelegate() = default;
3873
3874 virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0;
3875 };
3876
3877 class Menu : public WindowDelegate {
3878 public:
3879 enum class Type { Invalid, Bar, Item, Separator };
3880
3881 // Menubar or separator constructor
3882 Menu(Type type);
3883
3884 // Menuitem constructor
3885 Menu(const char *name, const char *key_name, int key_value,
3886 uint64_t identifier);
3887
3888 ~Menu() override = default;
3889
GetDelegate() const3890 const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; }
3891
SetDelegate(const MenuDelegateSP & delegate_sp)3892 void SetDelegate(const MenuDelegateSP &delegate_sp) {
3893 m_delegate_sp = delegate_sp;
3894 }
3895
3896 void RecalculateNameLengths();
3897
3898 void AddSubmenu(const MenuSP &menu_sp);
3899
3900 int DrawAndRunMenu(Window &window);
3901
3902 void DrawMenuTitle(Window &window, bool highlight);
3903
3904 bool WindowDelegateDraw(Window &window, bool force) override;
3905
3906 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
3907
ActionPrivate(Menu & menu)3908 MenuActionResult ActionPrivate(Menu &menu) {
3909 MenuActionResult result = MenuActionResult::NotHandled;
3910 if (m_delegate_sp) {
3911 result = m_delegate_sp->MenuDelegateAction(menu);
3912 if (result != MenuActionResult::NotHandled)
3913 return result;
3914 } else if (m_parent) {
3915 result = m_parent->ActionPrivate(menu);
3916 if (result != MenuActionResult::NotHandled)
3917 return result;
3918 }
3919 return m_canned_result;
3920 }
3921
Action()3922 MenuActionResult Action() {
3923 // Call the recursive action so it can try to handle it with the menu
3924 // delegate, and if not, try our parent menu
3925 return ActionPrivate(*this);
3926 }
3927
SetCannedResult(MenuActionResult result)3928 void SetCannedResult(MenuActionResult result) { m_canned_result = result; }
3929
GetSubmenus()3930 Menus &GetSubmenus() { return m_submenus; }
3931
GetSubmenus() const3932 const Menus &GetSubmenus() const { return m_submenus; }
3933
GetSelectedSubmenuIndex() const3934 int GetSelectedSubmenuIndex() const { return m_selected; }
3935
SetSelectedSubmenuIndex(int idx)3936 void SetSelectedSubmenuIndex(int idx) { m_selected = idx; }
3937
GetType() const3938 Type GetType() const { return m_type; }
3939
GetStartingColumn() const3940 int GetStartingColumn() const { return m_start_col; }
3941
SetStartingColumn(int col)3942 void SetStartingColumn(int col) { m_start_col = col; }
3943
GetKeyValue() const3944 int GetKeyValue() const { return m_key_value; }
3945
GetName()3946 std::string &GetName() { return m_name; }
3947
GetDrawWidth() const3948 int GetDrawWidth() const {
3949 return m_max_submenu_name_length + m_max_submenu_key_name_length + 8;
3950 }
3951
GetIdentifier() const3952 uint64_t GetIdentifier() const { return m_identifier; }
3953
SetIdentifier(uint64_t identifier)3954 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
3955
3956 protected:
3957 std::string m_name;
3958 std::string m_key_name;
3959 uint64_t m_identifier;
3960 Type m_type;
3961 int m_key_value;
3962 int m_start_col;
3963 int m_max_submenu_name_length;
3964 int m_max_submenu_key_name_length;
3965 int m_selected;
3966 Menu *m_parent;
3967 Menus m_submenus;
3968 WindowSP m_menu_window_sp;
3969 MenuActionResult m_canned_result;
3970 MenuDelegateSP m_delegate_sp;
3971 };
3972
3973 // Menubar or separator constructor
Menu(Type type)3974 Menu::Menu(Type type)
3975 : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0),
3976 m_start_col(0), m_max_submenu_name_length(0),
3977 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
3978 m_submenus(), m_canned_result(MenuActionResult::NotHandled),
3979 m_delegate_sp() {}
3980
3981 // Menuitem constructor
Menu(const char * name,const char * key_name,int key_value,uint64_t identifier)3982 Menu::Menu(const char *name, const char *key_name, int key_value,
3983 uint64_t identifier)
3984 : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid),
3985 m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0),
3986 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
3987 m_submenus(), m_canned_result(MenuActionResult::NotHandled),
3988 m_delegate_sp() {
3989 if (name && name[0]) {
3990 m_name = name;
3991 m_type = Type::Item;
3992 if (key_name && key_name[0])
3993 m_key_name = key_name;
3994 } else {
3995 m_type = Type::Separator;
3996 }
3997 }
3998
RecalculateNameLengths()3999 void Menu::RecalculateNameLengths() {
4000 m_max_submenu_name_length = 0;
4001 m_max_submenu_key_name_length = 0;
4002 Menus &submenus = GetSubmenus();
4003 const size_t num_submenus = submenus.size();
4004 for (size_t i = 0; i < num_submenus; ++i) {
4005 Menu *submenu = submenus[i].get();
4006 if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size())
4007 m_max_submenu_name_length = submenu->m_name.size();
4008 if (static_cast<size_t>(m_max_submenu_key_name_length) <
4009 submenu->m_key_name.size())
4010 m_max_submenu_key_name_length = submenu->m_key_name.size();
4011 }
4012 }
4013
AddSubmenu(const MenuSP & menu_sp)4014 void Menu::AddSubmenu(const MenuSP &menu_sp) {
4015 menu_sp->m_parent = this;
4016 if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size())
4017 m_max_submenu_name_length = menu_sp->m_name.size();
4018 if (static_cast<size_t>(m_max_submenu_key_name_length) <
4019 menu_sp->m_key_name.size())
4020 m_max_submenu_key_name_length = menu_sp->m_key_name.size();
4021 m_submenus.push_back(menu_sp);
4022 }
4023
DrawMenuTitle(Window & window,bool highlight)4024 void Menu::DrawMenuTitle(Window &window, bool highlight) {
4025 if (m_type == Type::Separator) {
4026 window.MoveCursor(0, window.GetCursorY());
4027 window.PutChar(ACS_LTEE);
4028 int width = window.GetWidth();
4029 if (width > 2) {
4030 width -= 2;
4031 for (int i = 0; i < width; ++i)
4032 window.PutChar(ACS_HLINE);
4033 }
4034 window.PutChar(ACS_RTEE);
4035 } else {
4036 const int shortcut_key = m_key_value;
4037 bool underlined_shortcut = false;
4038 const attr_t highlight_attr = A_REVERSE;
4039 if (highlight)
4040 window.AttributeOn(highlight_attr);
4041 if (llvm::isPrint(shortcut_key)) {
4042 size_t lower_pos = m_name.find(tolower(shortcut_key));
4043 size_t upper_pos = m_name.find(toupper(shortcut_key));
4044 const char *name = m_name.c_str();
4045 size_t pos = std::min<size_t>(lower_pos, upper_pos);
4046 if (pos != std::string::npos) {
4047 underlined_shortcut = true;
4048 if (pos > 0) {
4049 window.PutCString(name, pos);
4050 name += pos;
4051 }
4052 const attr_t shortcut_attr = A_UNDERLINE | A_BOLD;
4053 window.AttributeOn(shortcut_attr);
4054 window.PutChar(name[0]);
4055 window.AttributeOff(shortcut_attr);
4056 name++;
4057 if (name[0])
4058 window.PutCString(name);
4059 }
4060 }
4061
4062 if (!underlined_shortcut) {
4063 window.PutCString(m_name.c_str());
4064 }
4065
4066 if (highlight)
4067 window.AttributeOff(highlight_attr);
4068
4069 if (m_key_name.empty()) {
4070 if (!underlined_shortcut && llvm::isPrint(m_key_value)) {
4071 window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
4072 window.Printf(" (%c)", m_key_value);
4073 window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
4074 }
4075 } else {
4076 window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
4077 window.Printf(" (%s)", m_key_name.c_str());
4078 window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
4079 }
4080 }
4081 }
4082
WindowDelegateDraw(Window & window,bool force)4083 bool Menu::WindowDelegateDraw(Window &window, bool force) {
4084 Menus &submenus = GetSubmenus();
4085 const size_t num_submenus = submenus.size();
4086 const int selected_idx = GetSelectedSubmenuIndex();
4087 Menu::Type menu_type = GetType();
4088 switch (menu_type) {
4089 case Menu::Type::Bar: {
4090 window.SetBackground(BlackOnWhite);
4091 window.MoveCursor(0, 0);
4092 for (size_t i = 0; i < num_submenus; ++i) {
4093 Menu *menu = submenus[i].get();
4094 if (i > 0)
4095 window.PutChar(' ');
4096 menu->SetStartingColumn(window.GetCursorX());
4097 window.PutCString("| ");
4098 menu->DrawMenuTitle(window, false);
4099 }
4100 window.PutCString(" |");
4101 } break;
4102
4103 case Menu::Type::Item: {
4104 int y = 1;
4105 int x = 3;
4106 // Draw the menu
4107 int cursor_x = 0;
4108 int cursor_y = 0;
4109 window.Erase();
4110 window.SetBackground(BlackOnWhite);
4111 window.Box();
4112 for (size_t i = 0; i < num_submenus; ++i) {
4113 const bool is_selected = (i == static_cast<size_t>(selected_idx));
4114 window.MoveCursor(x, y + i);
4115 if (is_selected) {
4116 // Remember where we want the cursor to be
4117 cursor_x = x - 1;
4118 cursor_y = y + i;
4119 }
4120 submenus[i]->DrawMenuTitle(window, is_selected);
4121 }
4122 window.MoveCursor(cursor_x, cursor_y);
4123 } break;
4124
4125 default:
4126 case Menu::Type::Separator:
4127 break;
4128 }
4129 return true; // Drawing handled...
4130 }
4131
WindowDelegateHandleChar(Window & window,int key)4132 HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) {
4133 HandleCharResult result = eKeyNotHandled;
4134
4135 Menus &submenus = GetSubmenus();
4136 const size_t num_submenus = submenus.size();
4137 const int selected_idx = GetSelectedSubmenuIndex();
4138 Menu::Type menu_type = GetType();
4139 if (menu_type == Menu::Type::Bar) {
4140 MenuSP run_menu_sp;
4141 switch (key) {
4142 case KEY_DOWN:
4143 case KEY_UP:
4144 // Show last menu or first menu
4145 if (selected_idx < static_cast<int>(num_submenus))
4146 run_menu_sp = submenus[selected_idx];
4147 else if (!submenus.empty())
4148 run_menu_sp = submenus.front();
4149 result = eKeyHandled;
4150 break;
4151
4152 case KEY_RIGHT:
4153 ++m_selected;
4154 if (m_selected >= static_cast<int>(num_submenus))
4155 m_selected = 0;
4156 if (m_selected < static_cast<int>(num_submenus))
4157 run_menu_sp = submenus[m_selected];
4158 else if (!submenus.empty())
4159 run_menu_sp = submenus.front();
4160 result = eKeyHandled;
4161 break;
4162
4163 case KEY_LEFT:
4164 --m_selected;
4165 if (m_selected < 0)
4166 m_selected = num_submenus - 1;
4167 if (m_selected < static_cast<int>(num_submenus))
4168 run_menu_sp = submenus[m_selected];
4169 else if (!submenus.empty())
4170 run_menu_sp = submenus.front();
4171 result = eKeyHandled;
4172 break;
4173
4174 default:
4175 for (size_t i = 0; i < num_submenus; ++i) {
4176 if (submenus[i]->GetKeyValue() == key) {
4177 SetSelectedSubmenuIndex(i);
4178 run_menu_sp = submenus[i];
4179 result = eKeyHandled;
4180 break;
4181 }
4182 }
4183 break;
4184 }
4185
4186 if (run_menu_sp) {
4187 // Run the action on this menu in case we need to populate the menu with
4188 // dynamic content and also in case check marks, and any other menu
4189 // decorations need to be calculated
4190 if (run_menu_sp->Action() == MenuActionResult::Quit)
4191 return eQuitApplication;
4192
4193 Rect menu_bounds;
4194 menu_bounds.origin.x = run_menu_sp->GetStartingColumn();
4195 menu_bounds.origin.y = 1;
4196 menu_bounds.size.width = run_menu_sp->GetDrawWidth();
4197 menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2;
4198 if (m_menu_window_sp)
4199 window.GetParent()->RemoveSubWindow(m_menu_window_sp.get());
4200
4201 m_menu_window_sp = window.GetParent()->CreateSubWindow(
4202 run_menu_sp->GetName().c_str(), menu_bounds, true);
4203 m_menu_window_sp->SetDelegate(run_menu_sp);
4204 }
4205 } else if (menu_type == Menu::Type::Item) {
4206 switch (key) {
4207 case KEY_DOWN:
4208 if (m_submenus.size() > 1) {
4209 const int start_select = m_selected;
4210 while (++m_selected != start_select) {
4211 if (static_cast<size_t>(m_selected) >= num_submenus)
4212 m_selected = 0;
4213 if (m_submenus[m_selected]->GetType() == Type::Separator)
4214 continue;
4215 else
4216 break;
4217 }
4218 return eKeyHandled;
4219 }
4220 break;
4221
4222 case KEY_UP:
4223 if (m_submenus.size() > 1) {
4224 const int start_select = m_selected;
4225 while (--m_selected != start_select) {
4226 if (m_selected < static_cast<int>(0))
4227 m_selected = num_submenus - 1;
4228 if (m_submenus[m_selected]->GetType() == Type::Separator)
4229 continue;
4230 else
4231 break;
4232 }
4233 return eKeyHandled;
4234 }
4235 break;
4236
4237 case KEY_RETURN:
4238 if (static_cast<size_t>(selected_idx) < num_submenus) {
4239 if (submenus[selected_idx]->Action() == MenuActionResult::Quit)
4240 return eQuitApplication;
4241 window.GetParent()->RemoveSubWindow(&window);
4242 return eKeyHandled;
4243 }
4244 break;
4245
4246 case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in
4247 // case other chars are entered for escaped sequences
4248 window.GetParent()->RemoveSubWindow(&window);
4249 return eKeyHandled;
4250
4251 default:
4252 for (size_t i = 0; i < num_submenus; ++i) {
4253 Menu *menu = submenus[i].get();
4254 if (menu->GetKeyValue() == key) {
4255 SetSelectedSubmenuIndex(i);
4256 window.GetParent()->RemoveSubWindow(&window);
4257 if (menu->Action() == MenuActionResult::Quit)
4258 return eQuitApplication;
4259 return eKeyHandled;
4260 }
4261 }
4262 break;
4263 }
4264 } else if (menu_type == Menu::Type::Separator) {
4265 }
4266 return result;
4267 }
4268
4269 class Application {
4270 public:
Application(FILE * in,FILE * out)4271 Application(FILE *in, FILE *out)
4272 : m_window_sp(), m_screen(nullptr), m_in(in), m_out(out) {}
4273
~Application()4274 ~Application() {
4275 m_window_delegates.clear();
4276 m_window_sp.reset();
4277 if (m_screen) {
4278 ::delscreen(m_screen);
4279 m_screen = nullptr;
4280 }
4281 }
4282
Initialize()4283 void Initialize() {
4284 m_screen = ::newterm(nullptr, m_out, m_in);
4285 ::start_color();
4286 ::curs_set(0);
4287 ::noecho();
4288 ::keypad(stdscr, TRUE);
4289 }
4290
Terminate()4291 void Terminate() { ::endwin(); }
4292
Run(Debugger & debugger)4293 void Run(Debugger &debugger) {
4294 bool done = false;
4295 int delay_in_tenths_of_a_second = 1;
4296
4297 // Alas the threading model in curses is a bit lame so we need to resort
4298 // to polling every 0.5 seconds. We could poll for stdin ourselves and
4299 // then pass the keys down but then we need to translate all of the escape
4300 // sequences ourselves. So we resort to polling for input because we need
4301 // to receive async process events while in this loop.
4302
4303 halfdelay(delay_in_tenths_of_a_second); // Poll using some number of
4304 // tenths of seconds seconds when
4305 // calling Window::GetChar()
4306
4307 ListenerSP listener_sp(
4308 Listener::MakeListener("lldb.IOHandler.curses.Application"));
4309 ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass());
4310 debugger.EnableForwardEvents(listener_sp);
4311
4312 m_update_screen = true;
4313 #if defined(__APPLE__)
4314 std::deque<int> escape_chars;
4315 #endif
4316
4317 while (!done) {
4318 if (m_update_screen) {
4319 m_window_sp->Draw(false);
4320 // All windows should be calling Window::DeferredRefresh() instead of
4321 // Window::Refresh() so we can do a single update and avoid any screen
4322 // blinking
4323 update_panels();
4324
4325 // Cursor hiding isn't working on MacOSX, so hide it in the top left
4326 // corner
4327 m_window_sp->MoveCursor(0, 0);
4328
4329 doupdate();
4330 m_update_screen = false;
4331 }
4332
4333 #if defined(__APPLE__)
4334 // Terminal.app doesn't map its function keys correctly, F1-F4 default
4335 // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if
4336 // possible
4337 int ch;
4338 if (escape_chars.empty())
4339 ch = m_window_sp->GetChar();
4340 else {
4341 ch = escape_chars.front();
4342 escape_chars.pop_front();
4343 }
4344 if (ch == KEY_ESCAPE) {
4345 int ch2 = m_window_sp->GetChar();
4346 if (ch2 == 'O') {
4347 int ch3 = m_window_sp->GetChar();
4348 switch (ch3) {
4349 case 'P':
4350 ch = KEY_F(1);
4351 break;
4352 case 'Q':
4353 ch = KEY_F(2);
4354 break;
4355 case 'R':
4356 ch = KEY_F(3);
4357 break;
4358 case 'S':
4359 ch = KEY_F(4);
4360 break;
4361 default:
4362 escape_chars.push_back(ch2);
4363 if (ch3 != -1)
4364 escape_chars.push_back(ch3);
4365 break;
4366 }
4367 } else if (ch2 != -1)
4368 escape_chars.push_back(ch2);
4369 }
4370 #else
4371 int ch = m_window_sp->GetChar();
4372
4373 #endif
4374 if (ch == -1) {
4375 if (feof(m_in) || ferror(m_in)) {
4376 done = true;
4377 } else {
4378 // Just a timeout from using halfdelay(), check for events
4379 EventSP event_sp;
4380 while (listener_sp->PeekAtNextEvent()) {
4381 listener_sp->GetEvent(event_sp, std::chrono::seconds(0));
4382
4383 if (event_sp) {
4384 Broadcaster *broadcaster = event_sp->GetBroadcaster();
4385 if (broadcaster) {
4386 // uint32_t event_type = event_sp->GetType();
4387 ConstString broadcaster_class(
4388 broadcaster->GetBroadcasterClass());
4389 if (broadcaster_class == broadcaster_class_process) {
4390 m_update_screen = true;
4391 continue; // Don't get any key, just update our view
4392 }
4393 }
4394 }
4395 }
4396 }
4397 } else {
4398 HandleCharResult key_result = m_window_sp->HandleChar(ch);
4399 switch (key_result) {
4400 case eKeyHandled:
4401 m_update_screen = true;
4402 break;
4403 case eKeyNotHandled:
4404 if (ch == 12) { // Ctrl+L, force full redraw
4405 redrawwin(m_window_sp->get());
4406 m_update_screen = true;
4407 }
4408 break;
4409 case eQuitApplication:
4410 done = true;
4411 break;
4412 }
4413 }
4414 }
4415
4416 debugger.CancelForwardEvents(listener_sp);
4417 }
4418
GetMainWindow()4419 WindowSP &GetMainWindow() {
4420 if (!m_window_sp)
4421 m_window_sp = std::make_shared<Window>("main", stdscr, false);
4422 return m_window_sp;
4423 }
4424
TerminalSizeChanged()4425 void TerminalSizeChanged() {
4426 ::endwin();
4427 ::refresh();
4428 Rect content_bounds = m_window_sp->GetFrame();
4429 m_window_sp->SetBounds(content_bounds);
4430 if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar"))
4431 menubar_window_sp->SetBounds(content_bounds.MakeMenuBar());
4432 if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status"))
4433 status_window_sp->SetBounds(content_bounds.MakeStatusBar());
4434
4435 WindowSP source_window_sp = m_window_sp->FindSubWindow("Source");
4436 WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables");
4437 WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers");
4438 WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads");
4439
4440 Rect threads_bounds;
4441 Rect source_variables_bounds;
4442 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
4443 threads_bounds);
4444 if (threads_window_sp)
4445 threads_window_sp->SetBounds(threads_bounds);
4446 else
4447 source_variables_bounds = content_bounds;
4448
4449 Rect source_bounds;
4450 Rect variables_registers_bounds;
4451 source_variables_bounds.HorizontalSplitPercentage(
4452 0.70, source_bounds, variables_registers_bounds);
4453 if (variables_window_sp || registers_window_sp) {
4454 if (variables_window_sp && registers_window_sp) {
4455 Rect variables_bounds;
4456 Rect registers_bounds;
4457 variables_registers_bounds.VerticalSplitPercentage(
4458 0.50, variables_bounds, registers_bounds);
4459 variables_window_sp->SetBounds(variables_bounds);
4460 registers_window_sp->SetBounds(registers_bounds);
4461 } else if (variables_window_sp) {
4462 variables_window_sp->SetBounds(variables_registers_bounds);
4463 } else {
4464 registers_window_sp->SetBounds(variables_registers_bounds);
4465 }
4466 } else {
4467 source_bounds = source_variables_bounds;
4468 }
4469
4470 source_window_sp->SetBounds(source_bounds);
4471
4472 touchwin(stdscr);
4473 redrawwin(m_window_sp->get());
4474 m_update_screen = true;
4475 }
4476
4477 protected:
4478 WindowSP m_window_sp;
4479 WindowDelegates m_window_delegates;
4480 SCREEN *m_screen;
4481 FILE *m_in;
4482 FILE *m_out;
4483 bool m_update_screen = false;
4484 };
4485
4486 } // namespace curses
4487
4488 using namespace curses;
4489
4490 struct Row {
4491 ValueObjectUpdater value;
4492 Row *parent;
4493 // The process stop ID when the children were calculated.
4494 uint32_t children_stop_id = 0;
4495 int row_idx = 0;
4496 int x = 1;
4497 int y = 1;
4498 bool might_have_children;
4499 bool expanded = false;
4500 bool calculated_children = false;
4501 std::vector<Row> children;
4502
RowRow4503 Row(const ValueObjectSP &v, Row *p)
4504 : value(v), parent(p),
4505 might_have_children(v ? v->MightHaveChildren() : false) {}
4506
GetDepthRow4507 size_t GetDepth() const {
4508 if (parent)
4509 return 1 + parent->GetDepth();
4510 return 0;
4511 }
4512
ExpandRow4513 void Expand() { expanded = true; }
4514
GetChildrenRow4515 std::vector<Row> &GetChildren() {
4516 ProcessSP process_sp = value.GetProcessSP();
4517 auto stop_id = process_sp->GetStopID();
4518 if (process_sp && stop_id != children_stop_id) {
4519 children_stop_id = stop_id;
4520 calculated_children = false;
4521 }
4522 if (!calculated_children) {
4523 children.clear();
4524 calculated_children = true;
4525 ValueObjectSP valobj = value.GetSP();
4526 if (valobj) {
4527 const size_t num_children = valobj->GetNumChildren();
4528 for (size_t i = 0; i < num_children; ++i) {
4529 children.push_back(Row(valobj->GetChildAtIndex(i, true), this));
4530 }
4531 }
4532 }
4533 return children;
4534 }
4535
UnexpandRow4536 void Unexpand() {
4537 expanded = false;
4538 calculated_children = false;
4539 children.clear();
4540 }
4541
DrawTreeRow4542 void DrawTree(Window &window) {
4543 if (parent)
4544 parent->DrawTreeForChild(window, this, 0);
4545
4546 if (might_have_children) {
4547 // It we can get UTF8 characters to work we should try to use the
4548 // "symbol" UTF8 string below
4549 // const char *symbol = "";
4550 // if (row.expanded)
4551 // symbol = "\xe2\x96\xbd ";
4552 // else
4553 // symbol = "\xe2\x96\xb7 ";
4554 // window.PutCString (symbol);
4555
4556 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v'
4557 // or '>' character...
4558 // if (expanded)
4559 // window.PutChar (ACS_DARROW);
4560 // else
4561 // window.PutChar (ACS_RARROW);
4562 // Since we can't find any good looking right arrow/down arrow symbols,
4563 // just use a diamond...
4564 window.PutChar(ACS_DIAMOND);
4565 window.PutChar(ACS_HLINE);
4566 }
4567 }
4568
DrawTreeForChildRow4569 void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) {
4570 if (parent)
4571 parent->DrawTreeForChild(window, this, reverse_depth + 1);
4572
4573 if (&GetChildren().back() == child) {
4574 // Last child
4575 if (reverse_depth == 0) {
4576 window.PutChar(ACS_LLCORNER);
4577 window.PutChar(ACS_HLINE);
4578 } else {
4579 window.PutChar(' ');
4580 window.PutChar(' ');
4581 }
4582 } else {
4583 if (reverse_depth == 0) {
4584 window.PutChar(ACS_LTEE);
4585 window.PutChar(ACS_HLINE);
4586 } else {
4587 window.PutChar(ACS_VLINE);
4588 window.PutChar(' ');
4589 }
4590 }
4591 }
4592 };
4593
4594 struct DisplayOptions {
4595 bool show_types;
4596 };
4597
4598 class TreeItem;
4599
4600 class TreeDelegate {
4601 public:
4602 TreeDelegate() = default;
4603 virtual ~TreeDelegate() = default;
4604
4605 virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0;
4606 virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0;
TreeDelegateUpdateSelection(TreeItem & root,int & selection_index,TreeItem * & selected_item)4607 virtual void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
4608 TreeItem *&selected_item) {
4609 return;
4610 }
4611 // This is invoked when a tree item is selected. If true is returned, the
4612 // views are updated.
4613 virtual bool TreeDelegateItemSelected(TreeItem &item) = 0;
TreeDelegateExpandRootByDefault()4614 virtual bool TreeDelegateExpandRootByDefault() { return false; }
4615 // This is mostly useful for root tree delegates. If false is returned,
4616 // drawing will be skipped completely. This is needed, for instance, in
4617 // skipping drawing of the threads tree if there is no running process.
TreeDelegateShouldDraw()4618 virtual bool TreeDelegateShouldDraw() { return true; }
4619 };
4620
4621 typedef std::shared_ptr<TreeDelegate> TreeDelegateSP;
4622
4623 class TreeItem {
4624 public:
TreeItem(TreeItem * parent,TreeDelegate & delegate,bool might_have_children)4625 TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children)
4626 : m_parent(parent), m_delegate(delegate), m_user_data(nullptr),
4627 m_identifier(0), m_row_idx(-1), m_children(),
4628 m_might_have_children(might_have_children), m_is_expanded(false) {
4629 if (m_parent == nullptr)
4630 m_is_expanded = m_delegate.TreeDelegateExpandRootByDefault();
4631 }
4632
operator =(const TreeItem & rhs)4633 TreeItem &operator=(const TreeItem &rhs) {
4634 if (this != &rhs) {
4635 m_parent = rhs.m_parent;
4636 m_delegate = rhs.m_delegate;
4637 m_user_data = rhs.m_user_data;
4638 m_identifier = rhs.m_identifier;
4639 m_row_idx = rhs.m_row_idx;
4640 m_children = rhs.m_children;
4641 m_might_have_children = rhs.m_might_have_children;
4642 m_is_expanded = rhs.m_is_expanded;
4643 }
4644 return *this;
4645 }
4646
4647 TreeItem(const TreeItem &) = default;
4648
GetDepth() const4649 size_t GetDepth() const {
4650 if (m_parent)
4651 return 1 + m_parent->GetDepth();
4652 return 0;
4653 }
4654
GetRowIndex() const4655 int GetRowIndex() const { return m_row_idx; }
4656
ClearChildren()4657 void ClearChildren() { m_children.clear(); }
4658
Resize(size_t n,const TreeItem & t)4659 void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); }
4660
operator [](size_t i)4661 TreeItem &operator[](size_t i) { return m_children[i]; }
4662
SetRowIndex(int row_idx)4663 void SetRowIndex(int row_idx) { m_row_idx = row_idx; }
4664
GetNumChildren()4665 size_t GetNumChildren() {
4666 m_delegate.TreeDelegateGenerateChildren(*this);
4667 return m_children.size();
4668 }
4669
ItemWasSelected()4670 void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); }
4671
CalculateRowIndexes(int & row_idx)4672 void CalculateRowIndexes(int &row_idx) {
4673 SetRowIndex(row_idx);
4674 ++row_idx;
4675
4676 const bool expanded = IsExpanded();
4677
4678 // The root item must calculate its children, or we must calculate the
4679 // number of children if the item is expanded
4680 if (m_parent == nullptr || expanded)
4681 GetNumChildren();
4682
4683 for (auto &item : m_children) {
4684 if (expanded)
4685 item.CalculateRowIndexes(row_idx);
4686 else
4687 item.SetRowIndex(-1);
4688 }
4689 }
4690
GetParent()4691 TreeItem *GetParent() { return m_parent; }
4692
IsExpanded() const4693 bool IsExpanded() const { return m_is_expanded; }
4694
Expand()4695 void Expand() { m_is_expanded = true; }
4696
Unexpand()4697 void Unexpand() { m_is_expanded = false; }
4698
Draw(Window & window,const int first_visible_row,const uint32_t selected_row_idx,int & row_idx,int & num_rows_left)4699 bool Draw(Window &window, const int first_visible_row,
4700 const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) {
4701 if (num_rows_left <= 0)
4702 return false;
4703
4704 if (m_row_idx >= first_visible_row) {
4705 window.MoveCursor(2, row_idx + 1);
4706
4707 if (m_parent)
4708 m_parent->DrawTreeForChild(window, this, 0);
4709
4710 if (m_might_have_children) {
4711 // It we can get UTF8 characters to work we should try to use the
4712 // "symbol" UTF8 string below
4713 // const char *symbol = "";
4714 // if (row.expanded)
4715 // symbol = "\xe2\x96\xbd ";
4716 // else
4717 // symbol = "\xe2\x96\xb7 ";
4718 // window.PutCString (symbol);
4719
4720 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a
4721 // 'v' or '>' character...
4722 // if (expanded)
4723 // window.PutChar (ACS_DARROW);
4724 // else
4725 // window.PutChar (ACS_RARROW);
4726 // Since we can't find any good looking right arrow/down arrow symbols,
4727 // just use a diamond...
4728 window.PutChar(ACS_DIAMOND);
4729 window.PutChar(ACS_HLINE);
4730 }
4731 bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) &&
4732 window.IsActive();
4733
4734 if (highlight)
4735 window.AttributeOn(A_REVERSE);
4736
4737 m_delegate.TreeDelegateDrawTreeItem(*this, window);
4738
4739 if (highlight)
4740 window.AttributeOff(A_REVERSE);
4741 ++row_idx;
4742 --num_rows_left;
4743 }
4744
4745 if (num_rows_left <= 0)
4746 return false; // We are done drawing...
4747
4748 if (IsExpanded()) {
4749 for (auto &item : m_children) {
4750 // If we displayed all the rows and item.Draw() returns false we are
4751 // done drawing and can exit this for loop
4752 if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx,
4753 num_rows_left))
4754 break;
4755 }
4756 }
4757 return num_rows_left >= 0; // Return true if not done drawing yet
4758 }
4759
DrawTreeForChild(Window & window,TreeItem * child,uint32_t reverse_depth)4760 void DrawTreeForChild(Window &window, TreeItem *child,
4761 uint32_t reverse_depth) {
4762 if (m_parent)
4763 m_parent->DrawTreeForChild(window, this, reverse_depth + 1);
4764
4765 if (&m_children.back() == child) {
4766 // Last child
4767 if (reverse_depth == 0) {
4768 window.PutChar(ACS_LLCORNER);
4769 window.PutChar(ACS_HLINE);
4770 } else {
4771 window.PutChar(' ');
4772 window.PutChar(' ');
4773 }
4774 } else {
4775 if (reverse_depth == 0) {
4776 window.PutChar(ACS_LTEE);
4777 window.PutChar(ACS_HLINE);
4778 } else {
4779 window.PutChar(ACS_VLINE);
4780 window.PutChar(' ');
4781 }
4782 }
4783 }
4784
GetItemForRowIndex(uint32_t row_idx)4785 TreeItem *GetItemForRowIndex(uint32_t row_idx) {
4786 if (static_cast<uint32_t>(m_row_idx) == row_idx)
4787 return this;
4788 if (m_children.empty())
4789 return nullptr;
4790 if (IsExpanded()) {
4791 for (auto &item : m_children) {
4792 TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx);
4793 if (selected_item_ptr)
4794 return selected_item_ptr;
4795 }
4796 }
4797 return nullptr;
4798 }
4799
GetUserData() const4800 void *GetUserData() const { return m_user_data; }
4801
SetUserData(void * user_data)4802 void SetUserData(void *user_data) { m_user_data = user_data; }
4803
GetIdentifier() const4804 uint64_t GetIdentifier() const { return m_identifier; }
4805
SetIdentifier(uint64_t identifier)4806 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
4807
GetText() const4808 const std::string &GetText() const { return m_text; }
4809
SetText(const char * text)4810 void SetText(const char *text) {
4811 if (text == nullptr) {
4812 m_text.clear();
4813 return;
4814 }
4815 m_text = text;
4816 }
4817
SetMightHaveChildren(bool b)4818 void SetMightHaveChildren(bool b) { m_might_have_children = b; }
4819
4820 protected:
4821 TreeItem *m_parent;
4822 TreeDelegate &m_delegate;
4823 void *m_user_data;
4824 uint64_t m_identifier;
4825 std::string m_text;
4826 int m_row_idx; // Zero based visible row index, -1 if not visible or for the
4827 // root item
4828 std::vector<TreeItem> m_children;
4829 bool m_might_have_children;
4830 bool m_is_expanded;
4831 };
4832
4833 class TreeWindowDelegate : public WindowDelegate {
4834 public:
TreeWindowDelegate(Debugger & debugger,const TreeDelegateSP & delegate_sp)4835 TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp)
4836 : m_debugger(debugger), m_delegate_sp(delegate_sp),
4837 m_root(nullptr, *delegate_sp, true), m_selected_item(nullptr),
4838 m_num_rows(0), m_selected_row_idx(0), m_first_visible_row(0),
4839 m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {}
4840
NumVisibleRows() const4841 int NumVisibleRows() const { return m_max_y - m_min_y; }
4842
WindowDelegateDraw(Window & window,bool force)4843 bool WindowDelegateDraw(Window &window, bool force) override {
4844 m_min_x = 2;
4845 m_min_y = 1;
4846 m_max_x = window.GetWidth() - 1;
4847 m_max_y = window.GetHeight() - 1;
4848
4849 window.Erase();
4850 window.DrawTitleBox(window.GetName());
4851
4852 if (!m_delegate_sp->TreeDelegateShouldDraw()) {
4853 m_selected_item = nullptr;
4854 return true;
4855 }
4856
4857 const int num_visible_rows = NumVisibleRows();
4858 m_num_rows = 0;
4859 m_root.CalculateRowIndexes(m_num_rows);
4860 m_delegate_sp->TreeDelegateUpdateSelection(m_root, m_selected_row_idx,
4861 m_selected_item);
4862
4863 // If we unexpanded while having something selected our total number of
4864 // rows is less than the num visible rows, then make sure we show all the
4865 // rows by setting the first visible row accordingly.
4866 if (m_first_visible_row > 0 && m_num_rows < num_visible_rows)
4867 m_first_visible_row = 0;
4868
4869 // Make sure the selected row is always visible
4870 if (m_selected_row_idx < m_first_visible_row)
4871 m_first_visible_row = m_selected_row_idx;
4872 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
4873 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
4874
4875 int row_idx = 0;
4876 int num_rows_left = num_visible_rows;
4877 m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx,
4878 num_rows_left);
4879 // Get the selected row
4880 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4881
4882 return true; // Drawing handled
4883 }
4884
WindowDelegateGetHelpText()4885 const char *WindowDelegateGetHelpText() override {
4886 return "Thread window keyboard shortcuts:";
4887 }
4888
WindowDelegateGetKeyHelp()4889 KeyHelp *WindowDelegateGetKeyHelp() override {
4890 static curses::KeyHelp g_source_view_key_help[] = {
4891 {KEY_UP, "Select previous item"},
4892 {KEY_DOWN, "Select next item"},
4893 {KEY_RIGHT, "Expand the selected item"},
4894 {KEY_LEFT,
4895 "Unexpand the selected item or select parent if not expanded"},
4896 {KEY_PPAGE, "Page up"},
4897 {KEY_NPAGE, "Page down"},
4898 {'h', "Show help dialog"},
4899 {' ', "Toggle item expansion"},
4900 {',', "Page up"},
4901 {'.', "Page down"},
4902 {'\0', nullptr}};
4903 return g_source_view_key_help;
4904 }
4905
WindowDelegateHandleChar(Window & window,int c)4906 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
4907 switch (c) {
4908 case ',':
4909 case KEY_PPAGE:
4910 // Page up key
4911 if (m_first_visible_row > 0) {
4912 if (m_first_visible_row > m_max_y)
4913 m_first_visible_row -= m_max_y;
4914 else
4915 m_first_visible_row = 0;
4916 m_selected_row_idx = m_first_visible_row;
4917 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4918 if (m_selected_item)
4919 m_selected_item->ItemWasSelected();
4920 }
4921 return eKeyHandled;
4922
4923 case '.':
4924 case KEY_NPAGE:
4925 // Page down key
4926 if (m_num_rows > m_max_y) {
4927 if (m_first_visible_row + m_max_y < m_num_rows) {
4928 m_first_visible_row += m_max_y;
4929 m_selected_row_idx = m_first_visible_row;
4930 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4931 if (m_selected_item)
4932 m_selected_item->ItemWasSelected();
4933 }
4934 }
4935 return eKeyHandled;
4936
4937 case KEY_UP:
4938 if (m_selected_row_idx > 0) {
4939 --m_selected_row_idx;
4940 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4941 if (m_selected_item)
4942 m_selected_item->ItemWasSelected();
4943 }
4944 return eKeyHandled;
4945
4946 case KEY_DOWN:
4947 if (m_selected_row_idx + 1 < m_num_rows) {
4948 ++m_selected_row_idx;
4949 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4950 if (m_selected_item)
4951 m_selected_item->ItemWasSelected();
4952 }
4953 return eKeyHandled;
4954
4955 case KEY_RIGHT:
4956 if (m_selected_item) {
4957 if (!m_selected_item->IsExpanded())
4958 m_selected_item->Expand();
4959 }
4960 return eKeyHandled;
4961
4962 case KEY_LEFT:
4963 if (m_selected_item) {
4964 if (m_selected_item->IsExpanded())
4965 m_selected_item->Unexpand();
4966 else if (m_selected_item->GetParent()) {
4967 m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex();
4968 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4969 if (m_selected_item)
4970 m_selected_item->ItemWasSelected();
4971 }
4972 }
4973 return eKeyHandled;
4974
4975 case ' ':
4976 // Toggle expansion state when SPACE is pressed
4977 if (m_selected_item) {
4978 if (m_selected_item->IsExpanded())
4979 m_selected_item->Unexpand();
4980 else
4981 m_selected_item->Expand();
4982 }
4983 return eKeyHandled;
4984
4985 case 'h':
4986 window.CreateHelpSubwindow();
4987 return eKeyHandled;
4988
4989 default:
4990 break;
4991 }
4992 return eKeyNotHandled;
4993 }
4994
4995 protected:
4996 Debugger &m_debugger;
4997 TreeDelegateSP m_delegate_sp;
4998 TreeItem m_root;
4999 TreeItem *m_selected_item;
5000 int m_num_rows;
5001 int m_selected_row_idx;
5002 int m_first_visible_row;
5003 int m_min_x;
5004 int m_min_y;
5005 int m_max_x;
5006 int m_max_y;
5007 };
5008
5009 // A tree delegate that just draws the text member of the tree item, it doesn't
5010 // have any children or actions.
5011 class TextTreeDelegate : public TreeDelegate {
5012 public:
TextTreeDelegate()5013 TextTreeDelegate() : TreeDelegate() {}
5014
5015 ~TextTreeDelegate() override = default;
5016
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5017 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5018 window.PutCStringTruncated(1, item.GetText().c_str());
5019 }
5020
TreeDelegateGenerateChildren(TreeItem & item)5021 void TreeDelegateGenerateChildren(TreeItem &item) override {}
5022
TreeDelegateItemSelected(TreeItem & item)5023 bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5024 };
5025
5026 class FrameTreeDelegate : public TreeDelegate {
5027 public:
FrameTreeDelegate()5028 FrameTreeDelegate() : TreeDelegate() {
5029 FormatEntity::Parse(
5030 "frame #${frame.index}: {${function.name}${function.pc-offset}}}",
5031 m_format);
5032 }
5033
5034 ~FrameTreeDelegate() override = default;
5035
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5036 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5037 Thread *thread = (Thread *)item.GetUserData();
5038 if (thread) {
5039 const uint64_t frame_idx = item.GetIdentifier();
5040 StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx);
5041 if (frame_sp) {
5042 StreamString strm;
5043 const SymbolContext &sc =
5044 frame_sp->GetSymbolContext(eSymbolContextEverything);
5045 ExecutionContext exe_ctx(frame_sp);
5046 if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr,
5047 nullptr, false, false)) {
5048 int right_pad = 1;
5049 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
5050 }
5051 }
5052 }
5053 }
5054
TreeDelegateGenerateChildren(TreeItem & item)5055 void TreeDelegateGenerateChildren(TreeItem &item) override {
5056 // No children for frames yet...
5057 }
5058
TreeDelegateItemSelected(TreeItem & item)5059 bool TreeDelegateItemSelected(TreeItem &item) override {
5060 Thread *thread = (Thread *)item.GetUserData();
5061 if (thread) {
5062 thread->GetProcess()->GetThreadList().SetSelectedThreadByID(
5063 thread->GetID());
5064 const uint64_t frame_idx = item.GetIdentifier();
5065 thread->SetSelectedFrameByIndex(frame_idx);
5066 return true;
5067 }
5068 return false;
5069 }
5070
5071 protected:
5072 FormatEntity::Entry m_format;
5073 };
5074
5075 class ThreadTreeDelegate : public TreeDelegate {
5076 public:
ThreadTreeDelegate(Debugger & debugger)5077 ThreadTreeDelegate(Debugger &debugger)
5078 : TreeDelegate(), m_debugger(debugger), m_tid(LLDB_INVALID_THREAD_ID),
5079 m_stop_id(UINT32_MAX) {
5080 FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop "
5081 "reason = ${thread.stop-reason}}",
5082 m_format);
5083 }
5084
5085 ~ThreadTreeDelegate() override = default;
5086
GetProcess()5087 ProcessSP GetProcess() {
5088 return m_debugger.GetCommandInterpreter()
5089 .GetExecutionContext()
5090 .GetProcessSP();
5091 }
5092
GetThread(const TreeItem & item)5093 ThreadSP GetThread(const TreeItem &item) {
5094 ProcessSP process_sp = GetProcess();
5095 if (process_sp)
5096 return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier());
5097 return ThreadSP();
5098 }
5099
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5100 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5101 ThreadSP thread_sp = GetThread(item);
5102 if (thread_sp) {
5103 StreamString strm;
5104 ExecutionContext exe_ctx(thread_sp);
5105 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
5106 nullptr, false, false)) {
5107 int right_pad = 1;
5108 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
5109 }
5110 }
5111 }
5112
TreeDelegateGenerateChildren(TreeItem & item)5113 void TreeDelegateGenerateChildren(TreeItem &item) override {
5114 ProcessSP process_sp = GetProcess();
5115 if (process_sp && process_sp->IsAlive()) {
5116 StateType state = process_sp->GetState();
5117 if (StateIsStoppedState(state, true)) {
5118 ThreadSP thread_sp = GetThread(item);
5119 if (thread_sp) {
5120 if (m_stop_id == process_sp->GetStopID() &&
5121 thread_sp->GetID() == m_tid)
5122 return; // Children are already up to date
5123 if (!m_frame_delegate_sp) {
5124 // Always expand the thread item the first time we show it
5125 m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>();
5126 }
5127
5128 m_stop_id = process_sp->GetStopID();
5129 m_tid = thread_sp->GetID();
5130
5131 TreeItem t(&item, *m_frame_delegate_sp, false);
5132 size_t num_frames = thread_sp->GetStackFrameCount();
5133 item.Resize(num_frames, t);
5134 for (size_t i = 0; i < num_frames; ++i) {
5135 item[i].SetUserData(thread_sp.get());
5136 item[i].SetIdentifier(i);
5137 }
5138 }
5139 return;
5140 }
5141 }
5142 item.ClearChildren();
5143 }
5144
TreeDelegateItemSelected(TreeItem & item)5145 bool TreeDelegateItemSelected(TreeItem &item) override {
5146 ProcessSP process_sp = GetProcess();
5147 if (process_sp && process_sp->IsAlive()) {
5148 StateType state = process_sp->GetState();
5149 if (StateIsStoppedState(state, true)) {
5150 ThreadSP thread_sp = GetThread(item);
5151 if (thread_sp) {
5152 ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList();
5153 std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex());
5154 ThreadSP selected_thread_sp = thread_list.GetSelectedThread();
5155 if (selected_thread_sp->GetID() != thread_sp->GetID()) {
5156 thread_list.SetSelectedThreadByID(thread_sp->GetID());
5157 return true;
5158 }
5159 }
5160 }
5161 }
5162 return false;
5163 }
5164
5165 protected:
5166 Debugger &m_debugger;
5167 std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp;
5168 lldb::user_id_t m_tid;
5169 uint32_t m_stop_id;
5170 FormatEntity::Entry m_format;
5171 };
5172
5173 class ThreadsTreeDelegate : public TreeDelegate {
5174 public:
ThreadsTreeDelegate(Debugger & debugger)5175 ThreadsTreeDelegate(Debugger &debugger)
5176 : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger),
5177 m_stop_id(UINT32_MAX), m_update_selection(false) {
5178 FormatEntity::Parse("process ${process.id}{, name = ${process.name}}",
5179 m_format);
5180 }
5181
5182 ~ThreadsTreeDelegate() override = default;
5183
GetProcess()5184 ProcessSP GetProcess() {
5185 return m_debugger.GetCommandInterpreter()
5186 .GetExecutionContext()
5187 .GetProcessSP();
5188 }
5189
TreeDelegateShouldDraw()5190 bool TreeDelegateShouldDraw() override {
5191 ProcessSP process = GetProcess();
5192 if (!process)
5193 return false;
5194
5195 if (StateIsRunningState(process->GetState()))
5196 return false;
5197
5198 return true;
5199 }
5200
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5201 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5202 ProcessSP process_sp = GetProcess();
5203 if (process_sp && process_sp->IsAlive()) {
5204 StreamString strm;
5205 ExecutionContext exe_ctx(process_sp);
5206 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
5207 nullptr, false, false)) {
5208 int right_pad = 1;
5209 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
5210 }
5211 }
5212 }
5213
TreeDelegateGenerateChildren(TreeItem & item)5214 void TreeDelegateGenerateChildren(TreeItem &item) override {
5215 ProcessSP process_sp = GetProcess();
5216 m_update_selection = false;
5217 if (process_sp && process_sp->IsAlive()) {
5218 StateType state = process_sp->GetState();
5219 if (StateIsStoppedState(state, true)) {
5220 const uint32_t stop_id = process_sp->GetStopID();
5221 if (m_stop_id == stop_id)
5222 return; // Children are already up to date
5223
5224 m_stop_id = stop_id;
5225 m_update_selection = true;
5226
5227 if (!m_thread_delegate_sp) {
5228 // Always expand the thread item the first time we show it
5229 // item.Expand();
5230 m_thread_delegate_sp =
5231 std::make_shared<ThreadTreeDelegate>(m_debugger);
5232 }
5233
5234 TreeItem t(&item, *m_thread_delegate_sp, false);
5235 ThreadList &threads = process_sp->GetThreadList();
5236 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
5237 ThreadSP selected_thread = threads.GetSelectedThread();
5238 size_t num_threads = threads.GetSize();
5239 item.Resize(num_threads, t);
5240 for (size_t i = 0; i < num_threads; ++i) {
5241 ThreadSP thread = threads.GetThreadAtIndex(i);
5242 item[i].SetIdentifier(thread->GetID());
5243 item[i].SetMightHaveChildren(true);
5244 if (selected_thread->GetID() == thread->GetID())
5245 item[i].Expand();
5246 }
5247 return;
5248 }
5249 }
5250 item.ClearChildren();
5251 }
5252
TreeDelegateUpdateSelection(TreeItem & root,int & selection_index,TreeItem * & selected_item)5253 void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
5254 TreeItem *&selected_item) override {
5255 if (!m_update_selection)
5256 return;
5257
5258 ProcessSP process_sp = GetProcess();
5259 if (!(process_sp && process_sp->IsAlive()))
5260 return;
5261
5262 StateType state = process_sp->GetState();
5263 if (!StateIsStoppedState(state, true))
5264 return;
5265
5266 ThreadList &threads = process_sp->GetThreadList();
5267 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
5268 ThreadSP selected_thread = threads.GetSelectedThread();
5269 size_t num_threads = threads.GetSize();
5270 for (size_t i = 0; i < num_threads; ++i) {
5271 ThreadSP thread = threads.GetThreadAtIndex(i);
5272 if (selected_thread->GetID() == thread->GetID()) {
5273 selected_item = &root[i][thread->GetSelectedFrameIndex()];
5274 selection_index = selected_item->GetRowIndex();
5275 return;
5276 }
5277 }
5278 }
5279
TreeDelegateItemSelected(TreeItem & item)5280 bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5281
TreeDelegateExpandRootByDefault()5282 bool TreeDelegateExpandRootByDefault() override { return true; }
5283
5284 protected:
5285 std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp;
5286 Debugger &m_debugger;
5287 uint32_t m_stop_id;
5288 bool m_update_selection;
5289 FormatEntity::Entry m_format;
5290 };
5291
5292 class BreakpointLocationTreeDelegate : public TreeDelegate {
5293 public:
BreakpointLocationTreeDelegate(Debugger & debugger)5294 BreakpointLocationTreeDelegate(Debugger &debugger)
5295 : TreeDelegate(), m_debugger(debugger) {}
5296
5297 ~BreakpointLocationTreeDelegate() override = default;
5298
GetProcess()5299 Process *GetProcess() {
5300 ExecutionContext exe_ctx(
5301 m_debugger.GetCommandInterpreter().GetExecutionContext());
5302 return exe_ctx.GetProcessPtr();
5303 }
5304
GetBreakpointLocation(const TreeItem & item)5305 BreakpointLocationSP GetBreakpointLocation(const TreeItem &item) {
5306 Breakpoint *breakpoint = (Breakpoint *)item.GetUserData();
5307 return breakpoint->GetLocationAtIndex(item.GetIdentifier());
5308 }
5309
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5310 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5311 BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item);
5312 Process *process = GetProcess();
5313 StreamString stream;
5314 stream.Printf("%i.%i: ", breakpoint_location->GetBreakpoint().GetID(),
5315 breakpoint_location->GetID());
5316 Address address = breakpoint_location->GetAddress();
5317 address.Dump(&stream, process, Address::DumpStyleResolvedDescription,
5318 Address::DumpStyleInvalid);
5319 window.PutCStringTruncated(1, stream.GetString().str().c_str());
5320 }
5321
ComputeDetailsList(BreakpointLocationSP breakpoint_location)5322 StringList ComputeDetailsList(BreakpointLocationSP breakpoint_location) {
5323 StringList details;
5324
5325 Address address = breakpoint_location->GetAddress();
5326 SymbolContext symbol_context;
5327 address.CalculateSymbolContext(&symbol_context);
5328
5329 if (symbol_context.module_sp) {
5330 StreamString module_stream;
5331 module_stream.PutCString("module = ");
5332 symbol_context.module_sp->GetFileSpec().Dump(
5333 module_stream.AsRawOstream());
5334 details.AppendString(module_stream.GetString());
5335 }
5336
5337 if (symbol_context.comp_unit != nullptr) {
5338 StreamString compile_unit_stream;
5339 compile_unit_stream.PutCString("compile unit = ");
5340 symbol_context.comp_unit->GetPrimaryFile().GetFilename().Dump(
5341 &compile_unit_stream);
5342 details.AppendString(compile_unit_stream.GetString());
5343
5344 if (symbol_context.function != nullptr) {
5345 StreamString function_stream;
5346 function_stream.PutCString("function = ");
5347 function_stream.PutCString(
5348 symbol_context.function->GetName().AsCString("<unknown>"));
5349 details.AppendString(function_stream.GetString());
5350 }
5351
5352 if (symbol_context.line_entry.line > 0) {
5353 StreamString location_stream;
5354 location_stream.PutCString("location = ");
5355 symbol_context.line_entry.DumpStopContext(&location_stream, true);
5356 details.AppendString(location_stream.GetString());
5357 }
5358
5359 } else {
5360 if (symbol_context.symbol) {
5361 StreamString symbol_stream;
5362 if (breakpoint_location->IsReExported())
5363 symbol_stream.PutCString("re-exported target = ");
5364 else
5365 symbol_stream.PutCString("symbol = ");
5366 symbol_stream.PutCString(
5367 symbol_context.symbol->GetName().AsCString("<unknown>"));
5368 details.AppendString(symbol_stream.GetString());
5369 }
5370 }
5371
5372 Process *process = GetProcess();
5373
5374 StreamString address_stream;
5375 address.Dump(&address_stream, process, Address::DumpStyleLoadAddress,
5376 Address::DumpStyleModuleWithFileAddress);
5377 details.AppendString(address_stream.GetString());
5378
5379 BreakpointSiteSP breakpoint_site = breakpoint_location->GetBreakpointSite();
5380 if (breakpoint_location->IsIndirect() && breakpoint_site) {
5381 Address resolved_address;
5382 resolved_address.SetLoadAddress(breakpoint_site->GetLoadAddress(),
5383 &breakpoint_location->GetTarget());
5384 Symbol *resolved_symbol = resolved_address.CalculateSymbolContextSymbol();
5385 if (resolved_symbol) {
5386 StreamString indirect_target_stream;
5387 indirect_target_stream.PutCString("indirect target = ");
5388 indirect_target_stream.PutCString(
5389 resolved_symbol->GetName().GetCString());
5390 details.AppendString(indirect_target_stream.GetString());
5391 }
5392 }
5393
5394 bool is_resolved = breakpoint_location->IsResolved();
5395 StreamString resolved_stream;
5396 resolved_stream.Printf("resolved = %s", is_resolved ? "true" : "false");
5397 details.AppendString(resolved_stream.GetString());
5398
5399 bool is_hardware = is_resolved && breakpoint_site->IsHardware();
5400 StreamString hardware_stream;
5401 hardware_stream.Printf("hardware = %s", is_hardware ? "true" : "false");
5402 details.AppendString(hardware_stream.GetString());
5403
5404 StreamString hit_count_stream;
5405 hit_count_stream.Printf("hit count = %-4u",
5406 breakpoint_location->GetHitCount());
5407 details.AppendString(hit_count_stream.GetString());
5408
5409 return details;
5410 }
5411
TreeDelegateGenerateChildren(TreeItem & item)5412 void TreeDelegateGenerateChildren(TreeItem &item) override {
5413 BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item);
5414 StringList details = ComputeDetailsList(breakpoint_location);
5415
5416 if (!m_string_delegate_sp)
5417 m_string_delegate_sp = std::make_shared<TextTreeDelegate>();
5418 TreeItem details_tree_item(&item, *m_string_delegate_sp, false);
5419
5420 item.Resize(details.GetSize(), details_tree_item);
5421 for (size_t i = 0; i < details.GetSize(); i++) {
5422 item[i].SetText(details.GetStringAtIndex(i));
5423 }
5424 }
5425
TreeDelegateItemSelected(TreeItem & item)5426 bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5427
5428 protected:
5429 Debugger &m_debugger;
5430 std::shared_ptr<TextTreeDelegate> m_string_delegate_sp;
5431 };
5432
5433 class BreakpointTreeDelegate : public TreeDelegate {
5434 public:
BreakpointTreeDelegate(Debugger & debugger)5435 BreakpointTreeDelegate(Debugger &debugger)
5436 : TreeDelegate(), m_debugger(debugger),
5437 m_breakpoint_location_delegate_sp() {}
5438
5439 ~BreakpointTreeDelegate() override = default;
5440
GetBreakpoint(const TreeItem & item)5441 BreakpointSP GetBreakpoint(const TreeItem &item) {
5442 TargetSP target = m_debugger.GetSelectedTarget();
5443 BreakpointList &breakpoints = target->GetBreakpointList(false);
5444 return breakpoints.GetBreakpointAtIndex(item.GetIdentifier());
5445 }
5446
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5447 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5448 BreakpointSP breakpoint = GetBreakpoint(item);
5449 StreamString stream;
5450 stream.Format("{0}: ", breakpoint->GetID());
5451 breakpoint->GetResolverDescription(&stream);
5452 breakpoint->GetFilterDescription(&stream);
5453 window.PutCStringTruncated(1, stream.GetString().str().c_str());
5454 }
5455
TreeDelegateGenerateChildren(TreeItem & item)5456 void TreeDelegateGenerateChildren(TreeItem &item) override {
5457 BreakpointSP breakpoint = GetBreakpoint(item);
5458
5459 if (!m_breakpoint_location_delegate_sp)
5460 m_breakpoint_location_delegate_sp =
5461 std::make_shared<BreakpointLocationTreeDelegate>(m_debugger);
5462 TreeItem breakpoint_location_tree_item(
5463 &item, *m_breakpoint_location_delegate_sp, true);
5464
5465 item.Resize(breakpoint->GetNumLocations(), breakpoint_location_tree_item);
5466 for (size_t i = 0; i < breakpoint->GetNumLocations(); i++) {
5467 item[i].SetIdentifier(i);
5468 item[i].SetUserData(breakpoint.get());
5469 }
5470 }
5471
TreeDelegateItemSelected(TreeItem & item)5472 bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5473
5474 protected:
5475 Debugger &m_debugger;
5476 std::shared_ptr<BreakpointLocationTreeDelegate>
5477 m_breakpoint_location_delegate_sp;
5478 };
5479
5480 class BreakpointsTreeDelegate : public TreeDelegate {
5481 public:
BreakpointsTreeDelegate(Debugger & debugger)5482 BreakpointsTreeDelegate(Debugger &debugger)
5483 : TreeDelegate(), m_debugger(debugger), m_breakpoint_delegate_sp() {}
5484
5485 ~BreakpointsTreeDelegate() override = default;
5486
TreeDelegateShouldDraw()5487 bool TreeDelegateShouldDraw() override {
5488 TargetSP target = m_debugger.GetSelectedTarget();
5489 if (!target)
5490 return false;
5491
5492 return true;
5493 }
5494
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)5495 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5496 window.PutCString("Breakpoints");
5497 }
5498
TreeDelegateGenerateChildren(TreeItem & item)5499 void TreeDelegateGenerateChildren(TreeItem &item) override {
5500 TargetSP target = m_debugger.GetSelectedTarget();
5501
5502 BreakpointList &breakpoints = target->GetBreakpointList(false);
5503 std::unique_lock<std::recursive_mutex> lock;
5504 breakpoints.GetListMutex(lock);
5505
5506 if (!m_breakpoint_delegate_sp)
5507 m_breakpoint_delegate_sp =
5508 std::make_shared<BreakpointTreeDelegate>(m_debugger);
5509 TreeItem breakpoint_tree_item(&item, *m_breakpoint_delegate_sp, true);
5510
5511 item.Resize(breakpoints.GetSize(), breakpoint_tree_item);
5512 for (size_t i = 0; i < breakpoints.GetSize(); i++) {
5513 item[i].SetIdentifier(i);
5514 }
5515 }
5516
TreeDelegateItemSelected(TreeItem & item)5517 bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5518
TreeDelegateExpandRootByDefault()5519 bool TreeDelegateExpandRootByDefault() override { return true; }
5520
5521 protected:
5522 Debugger &m_debugger;
5523 std::shared_ptr<BreakpointTreeDelegate> m_breakpoint_delegate_sp;
5524 };
5525
5526 class ValueObjectListDelegate : public WindowDelegate {
5527 public:
ValueObjectListDelegate()5528 ValueObjectListDelegate() : m_rows() {}
5529
ValueObjectListDelegate(ValueObjectList & valobj_list)5530 ValueObjectListDelegate(ValueObjectList &valobj_list)
5531 : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0),
5532 m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) {
5533 SetValues(valobj_list);
5534 }
5535
5536 ~ValueObjectListDelegate() override = default;
5537
SetValues(ValueObjectList & valobj_list)5538 void SetValues(ValueObjectList &valobj_list) {
5539 m_selected_row = nullptr;
5540 m_selected_row_idx = 0;
5541 m_first_visible_row = 0;
5542 m_num_rows = 0;
5543 m_rows.clear();
5544 for (auto &valobj_sp : valobj_list.GetObjects())
5545 m_rows.push_back(Row(valobj_sp, nullptr));
5546 }
5547
WindowDelegateDraw(Window & window,bool force)5548 bool WindowDelegateDraw(Window &window, bool force) override {
5549 m_num_rows = 0;
5550 m_min_x = 2;
5551 m_min_y = 1;
5552 m_max_x = window.GetWidth() - 1;
5553 m_max_y = window.GetHeight() - 1;
5554
5555 window.Erase();
5556 window.DrawTitleBox(window.GetName());
5557
5558 const int num_visible_rows = NumVisibleRows();
5559 const int num_rows = CalculateTotalNumberRows(m_rows);
5560
5561 // If we unexpanded while having something selected our total number of
5562 // rows is less than the num visible rows, then make sure we show all the
5563 // rows by setting the first visible row accordingly.
5564 if (m_first_visible_row > 0 && num_rows < num_visible_rows)
5565 m_first_visible_row = 0;
5566
5567 // Make sure the selected row is always visible
5568 if (m_selected_row_idx < m_first_visible_row)
5569 m_first_visible_row = m_selected_row_idx;
5570 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
5571 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
5572
5573 DisplayRows(window, m_rows, g_options);
5574
5575 // Get the selected row
5576 m_selected_row = GetRowForRowIndex(m_selected_row_idx);
5577 // Keep the cursor on the selected row so the highlight and the cursor are
5578 // always on the same line
5579 if (m_selected_row)
5580 window.MoveCursor(m_selected_row->x, m_selected_row->y);
5581
5582 return true; // Drawing handled
5583 }
5584
WindowDelegateGetKeyHelp()5585 KeyHelp *WindowDelegateGetKeyHelp() override {
5586 static curses::KeyHelp g_source_view_key_help[] = {
5587 {KEY_UP, "Select previous item"},
5588 {KEY_DOWN, "Select next item"},
5589 {KEY_RIGHT, "Expand selected item"},
5590 {KEY_LEFT, "Unexpand selected item or select parent if not expanded"},
5591 {KEY_PPAGE, "Page up"},
5592 {KEY_NPAGE, "Page down"},
5593 {'A', "Format as annotated address"},
5594 {'b', "Format as binary"},
5595 {'B', "Format as hex bytes with ASCII"},
5596 {'c', "Format as character"},
5597 {'d', "Format as a signed integer"},
5598 {'D', "Format selected value using the default format for the type"},
5599 {'f', "Format as float"},
5600 {'h', "Show help dialog"},
5601 {'i', "Format as instructions"},
5602 {'o', "Format as octal"},
5603 {'p', "Format as pointer"},
5604 {'s', "Format as C string"},
5605 {'t', "Toggle showing/hiding type names"},
5606 {'u', "Format as an unsigned integer"},
5607 {'x', "Format as hex"},
5608 {'X', "Format as uppercase hex"},
5609 {' ', "Toggle item expansion"},
5610 {',', "Page up"},
5611 {'.', "Page down"},
5612 {'\0', nullptr}};
5613 return g_source_view_key_help;
5614 }
5615
WindowDelegateHandleChar(Window & window,int c)5616 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
5617 switch (c) {
5618 case 'x':
5619 case 'X':
5620 case 'o':
5621 case 's':
5622 case 'u':
5623 case 'd':
5624 case 'D':
5625 case 'i':
5626 case 'A':
5627 case 'p':
5628 case 'c':
5629 case 'b':
5630 case 'B':
5631 case 'f':
5632 // Change the format for the currently selected item
5633 if (m_selected_row) {
5634 auto valobj_sp = m_selected_row->value.GetSP();
5635 if (valobj_sp)
5636 valobj_sp->SetFormat(FormatForChar(c));
5637 }
5638 return eKeyHandled;
5639
5640 case 't':
5641 // Toggle showing type names
5642 g_options.show_types = !g_options.show_types;
5643 return eKeyHandled;
5644
5645 case ',':
5646 case KEY_PPAGE:
5647 // Page up key
5648 if (m_first_visible_row > 0) {
5649 if (static_cast<int>(m_first_visible_row) > m_max_y)
5650 m_first_visible_row -= m_max_y;
5651 else
5652 m_first_visible_row = 0;
5653 m_selected_row_idx = m_first_visible_row;
5654 }
5655 return eKeyHandled;
5656
5657 case '.':
5658 case KEY_NPAGE:
5659 // Page down key
5660 if (m_num_rows > static_cast<size_t>(m_max_y)) {
5661 if (m_first_visible_row + m_max_y < m_num_rows) {
5662 m_first_visible_row += m_max_y;
5663 m_selected_row_idx = m_first_visible_row;
5664 }
5665 }
5666 return eKeyHandled;
5667
5668 case KEY_UP:
5669 if (m_selected_row_idx > 0)
5670 --m_selected_row_idx;
5671 return eKeyHandled;
5672
5673 case KEY_DOWN:
5674 if (m_selected_row_idx + 1 < m_num_rows)
5675 ++m_selected_row_idx;
5676 return eKeyHandled;
5677
5678 case KEY_RIGHT:
5679 if (m_selected_row) {
5680 if (!m_selected_row->expanded)
5681 m_selected_row->Expand();
5682 }
5683 return eKeyHandled;
5684
5685 case KEY_LEFT:
5686 if (m_selected_row) {
5687 if (m_selected_row->expanded)
5688 m_selected_row->Unexpand();
5689 else if (m_selected_row->parent)
5690 m_selected_row_idx = m_selected_row->parent->row_idx;
5691 }
5692 return eKeyHandled;
5693
5694 case ' ':
5695 // Toggle expansion state when SPACE is pressed
5696 if (m_selected_row) {
5697 if (m_selected_row->expanded)
5698 m_selected_row->Unexpand();
5699 else
5700 m_selected_row->Expand();
5701 }
5702 return eKeyHandled;
5703
5704 case 'h':
5705 window.CreateHelpSubwindow();
5706 return eKeyHandled;
5707
5708 default:
5709 break;
5710 }
5711 return eKeyNotHandled;
5712 }
5713
5714 protected:
5715 std::vector<Row> m_rows;
5716 Row *m_selected_row = nullptr;
5717 uint32_t m_selected_row_idx = 0;
5718 uint32_t m_first_visible_row = 0;
5719 uint32_t m_num_rows = 0;
5720 int m_min_x;
5721 int m_min_y;
5722 int m_max_x = 0;
5723 int m_max_y = 0;
5724
FormatForChar(int c)5725 static Format FormatForChar(int c) {
5726 switch (c) {
5727 case 'x':
5728 return eFormatHex;
5729 case 'X':
5730 return eFormatHexUppercase;
5731 case 'o':
5732 return eFormatOctal;
5733 case 's':
5734 return eFormatCString;
5735 case 'u':
5736 return eFormatUnsigned;
5737 case 'd':
5738 return eFormatDecimal;
5739 case 'D':
5740 return eFormatDefault;
5741 case 'i':
5742 return eFormatInstruction;
5743 case 'A':
5744 return eFormatAddressInfo;
5745 case 'p':
5746 return eFormatPointer;
5747 case 'c':
5748 return eFormatChar;
5749 case 'b':
5750 return eFormatBinary;
5751 case 'B':
5752 return eFormatBytesWithASCII;
5753 case 'f':
5754 return eFormatFloat;
5755 }
5756 return eFormatDefault;
5757 }
5758
DisplayRowObject(Window & window,Row & row,DisplayOptions & options,bool highlight,bool last_child)5759 bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options,
5760 bool highlight, bool last_child) {
5761 ValueObject *valobj = row.value.GetSP().get();
5762
5763 if (valobj == nullptr)
5764 return false;
5765
5766 const char *type_name =
5767 options.show_types ? valobj->GetTypeName().GetCString() : nullptr;
5768 const char *name = valobj->GetName().GetCString();
5769 const char *value = valobj->GetValueAsCString();
5770 const char *summary = valobj->GetSummaryAsCString();
5771
5772 window.MoveCursor(row.x, row.y);
5773
5774 row.DrawTree(window);
5775
5776 if (highlight)
5777 window.AttributeOn(A_REVERSE);
5778
5779 if (type_name && type_name[0])
5780 window.PrintfTruncated(1, "(%s) ", type_name);
5781
5782 if (name && name[0])
5783 window.PutCStringTruncated(1, name);
5784
5785 attr_t changd_attr = 0;
5786 if (valobj->GetValueDidChange())
5787 changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD;
5788
5789 if (value && value[0]) {
5790 window.PutCStringTruncated(1, " = ");
5791 if (changd_attr)
5792 window.AttributeOn(changd_attr);
5793 window.PutCStringTruncated(1, value);
5794 if (changd_attr)
5795 window.AttributeOff(changd_attr);
5796 }
5797
5798 if (summary && summary[0]) {
5799 window.PutCStringTruncated(1, " ");
5800 if (changd_attr)
5801 window.AttributeOn(changd_attr);
5802 window.PutCStringTruncated(1, summary);
5803 if (changd_attr)
5804 window.AttributeOff(changd_attr);
5805 }
5806
5807 if (highlight)
5808 window.AttributeOff(A_REVERSE);
5809
5810 return true;
5811 }
5812
DisplayRows(Window & window,std::vector<Row> & rows,DisplayOptions & options)5813 void DisplayRows(Window &window, std::vector<Row> &rows,
5814 DisplayOptions &options) {
5815 // > 0x25B7
5816 // \/ 0x25BD
5817
5818 bool window_is_active = window.IsActive();
5819 for (auto &row : rows) {
5820 const bool last_child = row.parent && &rows[rows.size() - 1] == &row;
5821 // Save the row index in each Row structure
5822 row.row_idx = m_num_rows;
5823 if ((m_num_rows >= m_first_visible_row) &&
5824 ((m_num_rows - m_first_visible_row) <
5825 static_cast<size_t>(NumVisibleRows()))) {
5826 row.x = m_min_x;
5827 row.y = m_num_rows - m_first_visible_row + 1;
5828 if (DisplayRowObject(window, row, options,
5829 window_is_active &&
5830 m_num_rows == m_selected_row_idx,
5831 last_child)) {
5832 ++m_num_rows;
5833 } else {
5834 row.x = 0;
5835 row.y = 0;
5836 }
5837 } else {
5838 row.x = 0;
5839 row.y = 0;
5840 ++m_num_rows;
5841 }
5842
5843 auto &children = row.GetChildren();
5844 if (row.expanded && !children.empty()) {
5845 DisplayRows(window, children, options);
5846 }
5847 }
5848 }
5849
CalculateTotalNumberRows(std::vector<Row> & rows)5850 int CalculateTotalNumberRows(std::vector<Row> &rows) {
5851 int row_count = 0;
5852 for (auto &row : rows) {
5853 ++row_count;
5854 if (row.expanded)
5855 row_count += CalculateTotalNumberRows(row.GetChildren());
5856 }
5857 return row_count;
5858 }
5859
GetRowForRowIndexImpl(std::vector<Row> & rows,size_t & row_index)5860 static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) {
5861 for (auto &row : rows) {
5862 if (row_index == 0)
5863 return &row;
5864 else {
5865 --row_index;
5866 auto &children = row.GetChildren();
5867 if (row.expanded && !children.empty()) {
5868 Row *result = GetRowForRowIndexImpl(children, row_index);
5869 if (result)
5870 return result;
5871 }
5872 }
5873 }
5874 return nullptr;
5875 }
5876
GetRowForRowIndex(size_t row_index)5877 Row *GetRowForRowIndex(size_t row_index) {
5878 return GetRowForRowIndexImpl(m_rows, row_index);
5879 }
5880
NumVisibleRows() const5881 int NumVisibleRows() const { return m_max_y - m_min_y; }
5882
5883 static DisplayOptions g_options;
5884 };
5885
5886 class FrameVariablesWindowDelegate : public ValueObjectListDelegate {
5887 public:
FrameVariablesWindowDelegate(Debugger & debugger)5888 FrameVariablesWindowDelegate(Debugger &debugger)
5889 : ValueObjectListDelegate(), m_debugger(debugger),
5890 m_frame_block(nullptr) {}
5891
5892 ~FrameVariablesWindowDelegate() override = default;
5893
WindowDelegateGetHelpText()5894 const char *WindowDelegateGetHelpText() override {
5895 return "Frame variable window keyboard shortcuts:";
5896 }
5897
WindowDelegateDraw(Window & window,bool force)5898 bool WindowDelegateDraw(Window &window, bool force) override {
5899 ExecutionContext exe_ctx(
5900 m_debugger.GetCommandInterpreter().GetExecutionContext());
5901 Process *process = exe_ctx.GetProcessPtr();
5902 Block *frame_block = nullptr;
5903 StackFrame *frame = nullptr;
5904
5905 if (process) {
5906 StateType state = process->GetState();
5907 if (StateIsStoppedState(state, true)) {
5908 frame = exe_ctx.GetFramePtr();
5909 if (frame)
5910 frame_block = frame->GetFrameBlock();
5911 } else if (StateIsRunningState(state)) {
5912 return true; // Don't do any updating when we are running
5913 }
5914 }
5915
5916 ValueObjectList local_values;
5917 if (frame_block) {
5918 // Only update the variables if they have changed
5919 if (m_frame_block != frame_block) {
5920 m_frame_block = frame_block;
5921
5922 VariableList *locals = frame->GetVariableList(true);
5923 if (locals) {
5924 const DynamicValueType use_dynamic = eDynamicDontRunTarget;
5925 for (const VariableSP &local_sp : *locals) {
5926 ValueObjectSP value_sp =
5927 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic);
5928 if (value_sp) {
5929 ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue();
5930 if (synthetic_value_sp)
5931 local_values.Append(synthetic_value_sp);
5932 else
5933 local_values.Append(value_sp);
5934 }
5935 }
5936 // Update the values
5937 SetValues(local_values);
5938 }
5939 }
5940 } else {
5941 m_frame_block = nullptr;
5942 // Update the values with an empty list if there is no frame
5943 SetValues(local_values);
5944 }
5945
5946 return ValueObjectListDelegate::WindowDelegateDraw(window, force);
5947 }
5948
5949 protected:
5950 Debugger &m_debugger;
5951 Block *m_frame_block;
5952 };
5953
5954 class RegistersWindowDelegate : public ValueObjectListDelegate {
5955 public:
RegistersWindowDelegate(Debugger & debugger)5956 RegistersWindowDelegate(Debugger &debugger)
5957 : ValueObjectListDelegate(), m_debugger(debugger) {}
5958
5959 ~RegistersWindowDelegate() override = default;
5960
WindowDelegateGetHelpText()5961 const char *WindowDelegateGetHelpText() override {
5962 return "Register window keyboard shortcuts:";
5963 }
5964
WindowDelegateDraw(Window & window,bool force)5965 bool WindowDelegateDraw(Window &window, bool force) override {
5966 ExecutionContext exe_ctx(
5967 m_debugger.GetCommandInterpreter().GetExecutionContext());
5968 StackFrame *frame = exe_ctx.GetFramePtr();
5969
5970 ValueObjectList value_list;
5971 if (frame) {
5972 if (frame->GetStackID() != m_stack_id) {
5973 m_stack_id = frame->GetStackID();
5974 RegisterContextSP reg_ctx(frame->GetRegisterContext());
5975 if (reg_ctx) {
5976 const uint32_t num_sets = reg_ctx->GetRegisterSetCount();
5977 for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) {
5978 value_list.Append(
5979 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx));
5980 }
5981 }
5982 SetValues(value_list);
5983 }
5984 } else {
5985 Process *process = exe_ctx.GetProcessPtr();
5986 if (process && process->IsAlive())
5987 return true; // Don't do any updating if we are running
5988 else {
5989 // Update the values with an empty list if there is no process or the
5990 // process isn't alive anymore
5991 SetValues(value_list);
5992 }
5993 }
5994 return ValueObjectListDelegate::WindowDelegateDraw(window, force);
5995 }
5996
5997 protected:
5998 Debugger &m_debugger;
5999 StackID m_stack_id;
6000 };
6001
CursesKeyToCString(int ch)6002 static const char *CursesKeyToCString(int ch) {
6003 static char g_desc[32];
6004 if (ch >= KEY_F0 && ch < KEY_F0 + 64) {
6005 snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0);
6006 return g_desc;
6007 }
6008 switch (ch) {
6009 case KEY_DOWN:
6010 return "down";
6011 case KEY_UP:
6012 return "up";
6013 case KEY_LEFT:
6014 return "left";
6015 case KEY_RIGHT:
6016 return "right";
6017 case KEY_HOME:
6018 return "home";
6019 case KEY_BACKSPACE:
6020 return "backspace";
6021 case KEY_DL:
6022 return "delete-line";
6023 case KEY_IL:
6024 return "insert-line";
6025 case KEY_DC:
6026 return "delete-char";
6027 case KEY_IC:
6028 return "insert-char";
6029 case KEY_CLEAR:
6030 return "clear";
6031 case KEY_EOS:
6032 return "clear-to-eos";
6033 case KEY_EOL:
6034 return "clear-to-eol";
6035 case KEY_SF:
6036 return "scroll-forward";
6037 case KEY_SR:
6038 return "scroll-backward";
6039 case KEY_NPAGE:
6040 return "page-down";
6041 case KEY_PPAGE:
6042 return "page-up";
6043 case KEY_STAB:
6044 return "set-tab";
6045 case KEY_CTAB:
6046 return "clear-tab";
6047 case KEY_CATAB:
6048 return "clear-all-tabs";
6049 case KEY_ENTER:
6050 return "enter";
6051 case KEY_PRINT:
6052 return "print";
6053 case KEY_LL:
6054 return "lower-left key";
6055 case KEY_A1:
6056 return "upper left of keypad";
6057 case KEY_A3:
6058 return "upper right of keypad";
6059 case KEY_B2:
6060 return "center of keypad";
6061 case KEY_C1:
6062 return "lower left of keypad";
6063 case KEY_C3:
6064 return "lower right of keypad";
6065 case KEY_BTAB:
6066 return "back-tab key";
6067 case KEY_BEG:
6068 return "begin key";
6069 case KEY_CANCEL:
6070 return "cancel key";
6071 case KEY_CLOSE:
6072 return "close key";
6073 case KEY_COMMAND:
6074 return "command key";
6075 case KEY_COPY:
6076 return "copy key";
6077 case KEY_CREATE:
6078 return "create key";
6079 case KEY_END:
6080 return "end key";
6081 case KEY_EXIT:
6082 return "exit key";
6083 case KEY_FIND:
6084 return "find key";
6085 case KEY_HELP:
6086 return "help key";
6087 case KEY_MARK:
6088 return "mark key";
6089 case KEY_MESSAGE:
6090 return "message key";
6091 case KEY_MOVE:
6092 return "move key";
6093 case KEY_NEXT:
6094 return "next key";
6095 case KEY_OPEN:
6096 return "open key";
6097 case KEY_OPTIONS:
6098 return "options key";
6099 case KEY_PREVIOUS:
6100 return "previous key";
6101 case KEY_REDO:
6102 return "redo key";
6103 case KEY_REFERENCE:
6104 return "reference key";
6105 case KEY_REFRESH:
6106 return "refresh key";
6107 case KEY_REPLACE:
6108 return "replace key";
6109 case KEY_RESTART:
6110 return "restart key";
6111 case KEY_RESUME:
6112 return "resume key";
6113 case KEY_SAVE:
6114 return "save key";
6115 case KEY_SBEG:
6116 return "shifted begin key";
6117 case KEY_SCANCEL:
6118 return "shifted cancel key";
6119 case KEY_SCOMMAND:
6120 return "shifted command key";
6121 case KEY_SCOPY:
6122 return "shifted copy key";
6123 case KEY_SCREATE:
6124 return "shifted create key";
6125 case KEY_SDC:
6126 return "shifted delete-character key";
6127 case KEY_SDL:
6128 return "shifted delete-line key";
6129 case KEY_SELECT:
6130 return "select key";
6131 case KEY_SEND:
6132 return "shifted end key";
6133 case KEY_SEOL:
6134 return "shifted clear-to-end-of-line key";
6135 case KEY_SEXIT:
6136 return "shifted exit key";
6137 case KEY_SFIND:
6138 return "shifted find key";
6139 case KEY_SHELP:
6140 return "shifted help key";
6141 case KEY_SHOME:
6142 return "shifted home key";
6143 case KEY_SIC:
6144 return "shifted insert-character key";
6145 case KEY_SLEFT:
6146 return "shifted left-arrow key";
6147 case KEY_SMESSAGE:
6148 return "shifted message key";
6149 case KEY_SMOVE:
6150 return "shifted move key";
6151 case KEY_SNEXT:
6152 return "shifted next key";
6153 case KEY_SOPTIONS:
6154 return "shifted options key";
6155 case KEY_SPREVIOUS:
6156 return "shifted previous key";
6157 case KEY_SPRINT:
6158 return "shifted print key";
6159 case KEY_SREDO:
6160 return "shifted redo key";
6161 case KEY_SREPLACE:
6162 return "shifted replace key";
6163 case KEY_SRIGHT:
6164 return "shifted right-arrow key";
6165 case KEY_SRSUME:
6166 return "shifted resume key";
6167 case KEY_SSAVE:
6168 return "shifted save key";
6169 case KEY_SSUSPEND:
6170 return "shifted suspend key";
6171 case KEY_SUNDO:
6172 return "shifted undo key";
6173 case KEY_SUSPEND:
6174 return "suspend key";
6175 case KEY_UNDO:
6176 return "undo key";
6177 case KEY_MOUSE:
6178 return "Mouse event has occurred";
6179 case KEY_RESIZE:
6180 return "Terminal resize event";
6181 #ifdef KEY_EVENT
6182 case KEY_EVENT:
6183 return "We were interrupted by an event";
6184 #endif
6185 case KEY_RETURN:
6186 return "return";
6187 case ' ':
6188 return "space";
6189 case '\t':
6190 return "tab";
6191 case KEY_ESCAPE:
6192 return "escape";
6193 default:
6194 if (llvm::isPrint(ch))
6195 snprintf(g_desc, sizeof(g_desc), "%c", ch);
6196 else
6197 snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch);
6198 return g_desc;
6199 }
6200 return nullptr;
6201 }
6202
HelpDialogDelegate(const char * text,KeyHelp * key_help_array)6203 HelpDialogDelegate::HelpDialogDelegate(const char *text,
6204 KeyHelp *key_help_array)
6205 : m_text(), m_first_visible_line(0) {
6206 if (text && text[0]) {
6207 m_text.SplitIntoLines(text);
6208 m_text.AppendString("");
6209 }
6210 if (key_help_array) {
6211 for (KeyHelp *key = key_help_array; key->ch; ++key) {
6212 StreamString key_description;
6213 key_description.Printf("%10s - %s", CursesKeyToCString(key->ch),
6214 key->description);
6215 m_text.AppendString(key_description.GetString());
6216 }
6217 }
6218 }
6219
6220 HelpDialogDelegate::~HelpDialogDelegate() = default;
6221
WindowDelegateDraw(Window & window,bool force)6222 bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) {
6223 window.Erase();
6224 const int window_height = window.GetHeight();
6225 int x = 2;
6226 int y = 1;
6227 const int min_y = y;
6228 const int max_y = window_height - 1 - y;
6229 const size_t num_visible_lines = max_y - min_y + 1;
6230 const size_t num_lines = m_text.GetSize();
6231 const char *bottom_message;
6232 if (num_lines <= num_visible_lines)
6233 bottom_message = "Press any key to exit";
6234 else
6235 bottom_message = "Use arrows to scroll, any other key to exit";
6236 window.DrawTitleBox(window.GetName(), bottom_message);
6237 while (y <= max_y) {
6238 window.MoveCursor(x, y);
6239 window.PutCStringTruncated(
6240 1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y));
6241 ++y;
6242 }
6243 return true;
6244 }
6245
WindowDelegateHandleChar(Window & window,int key)6246 HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window,
6247 int key) {
6248 bool done = false;
6249 const size_t num_lines = m_text.GetSize();
6250 const size_t num_visible_lines = window.GetHeight() - 2;
6251
6252 if (num_lines <= num_visible_lines) {
6253 done = true;
6254 // If we have all lines visible and don't need scrolling, then any key
6255 // press will cause us to exit
6256 } else {
6257 switch (key) {
6258 case KEY_UP:
6259 if (m_first_visible_line > 0)
6260 --m_first_visible_line;
6261 break;
6262
6263 case KEY_DOWN:
6264 if (m_first_visible_line + num_visible_lines < num_lines)
6265 ++m_first_visible_line;
6266 break;
6267
6268 case KEY_PPAGE:
6269 case ',':
6270 if (m_first_visible_line > 0) {
6271 if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines)
6272 m_first_visible_line -= num_visible_lines;
6273 else
6274 m_first_visible_line = 0;
6275 }
6276 break;
6277
6278 case KEY_NPAGE:
6279 case '.':
6280 if (m_first_visible_line + num_visible_lines < num_lines) {
6281 m_first_visible_line += num_visible_lines;
6282 if (static_cast<size_t>(m_first_visible_line) > num_lines)
6283 m_first_visible_line = num_lines - num_visible_lines;
6284 }
6285 break;
6286
6287 default:
6288 done = true;
6289 break;
6290 }
6291 }
6292 if (done)
6293 window.GetParent()->RemoveSubWindow(&window);
6294 return eKeyHandled;
6295 }
6296
6297 class ApplicationDelegate : public WindowDelegate, public MenuDelegate {
6298 public:
6299 enum {
6300 eMenuID_LLDB = 1,
6301 eMenuID_LLDBAbout,
6302 eMenuID_LLDBExit,
6303
6304 eMenuID_Target,
6305 eMenuID_TargetCreate,
6306 eMenuID_TargetDelete,
6307
6308 eMenuID_Process,
6309 eMenuID_ProcessAttach,
6310 eMenuID_ProcessDetachResume,
6311 eMenuID_ProcessDetachSuspended,
6312 eMenuID_ProcessLaunch,
6313 eMenuID_ProcessContinue,
6314 eMenuID_ProcessHalt,
6315 eMenuID_ProcessKill,
6316
6317 eMenuID_Thread,
6318 eMenuID_ThreadStepIn,
6319 eMenuID_ThreadStepOver,
6320 eMenuID_ThreadStepOut,
6321
6322 eMenuID_View,
6323 eMenuID_ViewBacktrace,
6324 eMenuID_ViewRegisters,
6325 eMenuID_ViewSource,
6326 eMenuID_ViewVariables,
6327 eMenuID_ViewBreakpoints,
6328
6329 eMenuID_Help,
6330 eMenuID_HelpGUIHelp
6331 };
6332
ApplicationDelegate(Application & app,Debugger & debugger)6333 ApplicationDelegate(Application &app, Debugger &debugger)
6334 : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {}
6335
6336 ~ApplicationDelegate() override = default;
6337
WindowDelegateDraw(Window & window,bool force)6338 bool WindowDelegateDraw(Window &window, bool force) override {
6339 return false; // Drawing not handled, let standard window drawing happen
6340 }
6341
WindowDelegateHandleChar(Window & window,int key)6342 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
6343 switch (key) {
6344 case '\t':
6345 window.SelectNextWindowAsActive();
6346 return eKeyHandled;
6347
6348 case KEY_SHIFT_TAB:
6349 window.SelectPreviousWindowAsActive();
6350 return eKeyHandled;
6351
6352 case 'h':
6353 window.CreateHelpSubwindow();
6354 return eKeyHandled;
6355
6356 case KEY_ESCAPE:
6357 return eQuitApplication;
6358
6359 default:
6360 break;
6361 }
6362 return eKeyNotHandled;
6363 }
6364
WindowDelegateGetHelpText()6365 const char *WindowDelegateGetHelpText() override {
6366 return "Welcome to the LLDB curses GUI.\n\n"
6367 "Press the TAB key to change the selected view.\n"
6368 "Each view has its own keyboard shortcuts, press 'h' to open a "
6369 "dialog to display them.\n\n"
6370 "Common key bindings for all views:";
6371 }
6372
WindowDelegateGetKeyHelp()6373 KeyHelp *WindowDelegateGetKeyHelp() override {
6374 static curses::KeyHelp g_source_view_key_help[] = {
6375 {'\t', "Select next view"},
6376 {KEY_BTAB, "Select previous view"},
6377 {'h', "Show help dialog with view specific key bindings"},
6378 {',', "Page up"},
6379 {'.', "Page down"},
6380 {KEY_UP, "Select previous"},
6381 {KEY_DOWN, "Select next"},
6382 {KEY_LEFT, "Unexpand or select parent"},
6383 {KEY_RIGHT, "Expand"},
6384 {KEY_PPAGE, "Page up"},
6385 {KEY_NPAGE, "Page down"},
6386 {'\0', nullptr}};
6387 return g_source_view_key_help;
6388 }
6389
MenuDelegateAction(Menu & menu)6390 MenuActionResult MenuDelegateAction(Menu &menu) override {
6391 switch (menu.GetIdentifier()) {
6392 case eMenuID_TargetCreate: {
6393 WindowSP main_window_sp = m_app.GetMainWindow();
6394 FormDelegateSP form_delegate_sp =
6395 FormDelegateSP(new TargetCreateFormDelegate(m_debugger));
6396 Rect bounds = main_window_sp->GetCenteredRect(80, 19);
6397 WindowSP form_window_sp = main_window_sp->CreateSubWindow(
6398 form_delegate_sp->GetName().c_str(), bounds, true);
6399 WindowDelegateSP window_delegate_sp =
6400 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
6401 form_window_sp->SetDelegate(window_delegate_sp);
6402 return MenuActionResult::Handled;
6403 }
6404 case eMenuID_ThreadStepIn: {
6405 ExecutionContext exe_ctx =
6406 m_debugger.GetCommandInterpreter().GetExecutionContext();
6407 if (exe_ctx.HasThreadScope()) {
6408 Process *process = exe_ctx.GetProcessPtr();
6409 if (process && process->IsAlive() &&
6410 StateIsStoppedState(process->GetState(), true))
6411 exe_ctx.GetThreadRef().StepIn(true);
6412 }
6413 }
6414 return MenuActionResult::Handled;
6415
6416 case eMenuID_ThreadStepOut: {
6417 ExecutionContext exe_ctx =
6418 m_debugger.GetCommandInterpreter().GetExecutionContext();
6419 if (exe_ctx.HasThreadScope()) {
6420 Process *process = exe_ctx.GetProcessPtr();
6421 if (process && process->IsAlive() &&
6422 StateIsStoppedState(process->GetState(), true))
6423 exe_ctx.GetThreadRef().StepOut();
6424 }
6425 }
6426 return MenuActionResult::Handled;
6427
6428 case eMenuID_ThreadStepOver: {
6429 ExecutionContext exe_ctx =
6430 m_debugger.GetCommandInterpreter().GetExecutionContext();
6431 if (exe_ctx.HasThreadScope()) {
6432 Process *process = exe_ctx.GetProcessPtr();
6433 if (process && process->IsAlive() &&
6434 StateIsStoppedState(process->GetState(), true))
6435 exe_ctx.GetThreadRef().StepOver(true);
6436 }
6437 }
6438 return MenuActionResult::Handled;
6439
6440 case eMenuID_ProcessAttach: {
6441 WindowSP main_window_sp = m_app.GetMainWindow();
6442 FormDelegateSP form_delegate_sp = FormDelegateSP(
6443 new ProcessAttachFormDelegate(m_debugger, main_window_sp));
6444 Rect bounds = main_window_sp->GetCenteredRect(80, 22);
6445 WindowSP form_window_sp = main_window_sp->CreateSubWindow(
6446 form_delegate_sp->GetName().c_str(), bounds, true);
6447 WindowDelegateSP window_delegate_sp =
6448 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
6449 form_window_sp->SetDelegate(window_delegate_sp);
6450 return MenuActionResult::Handled;
6451 }
6452 case eMenuID_ProcessLaunch: {
6453 WindowSP main_window_sp = m_app.GetMainWindow();
6454 FormDelegateSP form_delegate_sp = FormDelegateSP(
6455 new ProcessLaunchFormDelegate(m_debugger, main_window_sp));
6456 Rect bounds = main_window_sp->GetCenteredRect(80, 22);
6457 WindowSP form_window_sp = main_window_sp->CreateSubWindow(
6458 form_delegate_sp->GetName().c_str(), bounds, true);
6459 WindowDelegateSP window_delegate_sp =
6460 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
6461 form_window_sp->SetDelegate(window_delegate_sp);
6462 return MenuActionResult::Handled;
6463 }
6464
6465 case eMenuID_ProcessContinue: {
6466 ExecutionContext exe_ctx =
6467 m_debugger.GetCommandInterpreter().GetExecutionContext();
6468 if (exe_ctx.HasProcessScope()) {
6469 Process *process = exe_ctx.GetProcessPtr();
6470 if (process && process->IsAlive() &&
6471 StateIsStoppedState(process->GetState(), true))
6472 process->Resume();
6473 }
6474 }
6475 return MenuActionResult::Handled;
6476
6477 case eMenuID_ProcessKill: {
6478 ExecutionContext exe_ctx =
6479 m_debugger.GetCommandInterpreter().GetExecutionContext();
6480 if (exe_ctx.HasProcessScope()) {
6481 Process *process = exe_ctx.GetProcessPtr();
6482 if (process && process->IsAlive())
6483 process->Destroy(false);
6484 }
6485 }
6486 return MenuActionResult::Handled;
6487
6488 case eMenuID_ProcessHalt: {
6489 ExecutionContext exe_ctx =
6490 m_debugger.GetCommandInterpreter().GetExecutionContext();
6491 if (exe_ctx.HasProcessScope()) {
6492 Process *process = exe_ctx.GetProcessPtr();
6493 if (process && process->IsAlive())
6494 process->Halt();
6495 }
6496 }
6497 return MenuActionResult::Handled;
6498
6499 case eMenuID_ProcessDetachResume:
6500 case eMenuID_ProcessDetachSuspended: {
6501 ExecutionContext exe_ctx =
6502 m_debugger.GetCommandInterpreter().GetExecutionContext();
6503 if (exe_ctx.HasProcessScope()) {
6504 Process *process = exe_ctx.GetProcessPtr();
6505 if (process && process->IsAlive())
6506 process->Detach(menu.GetIdentifier() ==
6507 eMenuID_ProcessDetachSuspended);
6508 }
6509 }
6510 return MenuActionResult::Handled;
6511
6512 case eMenuID_Process: {
6513 // Populate the menu with all of the threads if the process is stopped
6514 // when the Process menu gets selected and is about to display its
6515 // submenu.
6516 Menus &submenus = menu.GetSubmenus();
6517 ExecutionContext exe_ctx =
6518 m_debugger.GetCommandInterpreter().GetExecutionContext();
6519 Process *process = exe_ctx.GetProcessPtr();
6520 if (process && process->IsAlive() &&
6521 StateIsStoppedState(process->GetState(), true)) {
6522 if (submenus.size() == 7)
6523 menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
6524 else if (submenus.size() > 8)
6525 submenus.erase(submenus.begin() + 8, submenus.end());
6526
6527 ThreadList &threads = process->GetThreadList();
6528 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
6529 size_t num_threads = threads.GetSize();
6530 for (size_t i = 0; i < num_threads; ++i) {
6531 ThreadSP thread_sp = threads.GetThreadAtIndex(i);
6532 char menu_char = '\0';
6533 if (i < 9)
6534 menu_char = '1' + i;
6535 StreamString thread_menu_title;
6536 thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID());
6537 const char *thread_name = thread_sp->GetName();
6538 if (thread_name && thread_name[0])
6539 thread_menu_title.Printf(" %s", thread_name);
6540 else {
6541 const char *queue_name = thread_sp->GetQueueName();
6542 if (queue_name && queue_name[0])
6543 thread_menu_title.Printf(" %s", queue_name);
6544 }
6545 menu.AddSubmenu(
6546 MenuSP(new Menu(thread_menu_title.GetString().str().c_str(),
6547 nullptr, menu_char, thread_sp->GetID())));
6548 }
6549 } else if (submenus.size() > 7) {
6550 // Remove the separator and any other thread submenu items that were
6551 // previously added
6552 submenus.erase(submenus.begin() + 7, submenus.end());
6553 }
6554 // Since we are adding and removing items we need to recalculate the
6555 // name lengths
6556 menu.RecalculateNameLengths();
6557 }
6558 return MenuActionResult::Handled;
6559
6560 case eMenuID_ViewVariables: {
6561 WindowSP main_window_sp = m_app.GetMainWindow();
6562 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
6563 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
6564 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
6565 const Rect source_bounds = source_window_sp->GetBounds();
6566
6567 if (variables_window_sp) {
6568 const Rect variables_bounds = variables_window_sp->GetBounds();
6569
6570 main_window_sp->RemoveSubWindow(variables_window_sp.get());
6571
6572 if (registers_window_sp) {
6573 // We have a registers window, so give all the area back to the
6574 // registers window
6575 Rect registers_bounds = variables_bounds;
6576 registers_bounds.size.width = source_bounds.size.width;
6577 registers_window_sp->SetBounds(registers_bounds);
6578 } else {
6579 // We have no registers window showing so give the bottom area back
6580 // to the source view
6581 source_window_sp->Resize(source_bounds.size.width,
6582 source_bounds.size.height +
6583 variables_bounds.size.height);
6584 }
6585 } else {
6586 Rect new_variables_rect;
6587 if (registers_window_sp) {
6588 // We have a registers window so split the area of the registers
6589 // window into two columns where the left hand side will be the
6590 // variables and the right hand side will be the registers
6591 const Rect variables_bounds = registers_window_sp->GetBounds();
6592 Rect new_registers_rect;
6593 variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect,
6594 new_registers_rect);
6595 registers_window_sp->SetBounds(new_registers_rect);
6596 } else {
6597 // No registers window, grab the bottom part of the source window
6598 Rect new_source_rect;
6599 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
6600 new_variables_rect);
6601 source_window_sp->SetBounds(new_source_rect);
6602 }
6603 WindowSP new_window_sp = main_window_sp->CreateSubWindow(
6604 "Variables", new_variables_rect, false);
6605 new_window_sp->SetDelegate(
6606 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
6607 }
6608 touchwin(stdscr);
6609 }
6610 return MenuActionResult::Handled;
6611
6612 case eMenuID_ViewRegisters: {
6613 WindowSP main_window_sp = m_app.GetMainWindow();
6614 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
6615 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
6616 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
6617 const Rect source_bounds = source_window_sp->GetBounds();
6618
6619 if (registers_window_sp) {
6620 if (variables_window_sp) {
6621 const Rect variables_bounds = variables_window_sp->GetBounds();
6622
6623 // We have a variables window, so give all the area back to the
6624 // variables window
6625 variables_window_sp->Resize(variables_bounds.size.width +
6626 registers_window_sp->GetWidth(),
6627 variables_bounds.size.height);
6628 } else {
6629 // We have no variables window showing so give the bottom area back
6630 // to the source view
6631 source_window_sp->Resize(source_bounds.size.width,
6632 source_bounds.size.height +
6633 registers_window_sp->GetHeight());
6634 }
6635 main_window_sp->RemoveSubWindow(registers_window_sp.get());
6636 } else {
6637 Rect new_regs_rect;
6638 if (variables_window_sp) {
6639 // We have a variables window, split it into two columns where the
6640 // left hand side will be the variables and the right hand side will
6641 // be the registers
6642 const Rect variables_bounds = variables_window_sp->GetBounds();
6643 Rect new_vars_rect;
6644 variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect,
6645 new_regs_rect);
6646 variables_window_sp->SetBounds(new_vars_rect);
6647 } else {
6648 // No variables window, grab the bottom part of the source window
6649 Rect new_source_rect;
6650 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
6651 new_regs_rect);
6652 source_window_sp->SetBounds(new_source_rect);
6653 }
6654 WindowSP new_window_sp =
6655 main_window_sp->CreateSubWindow("Registers", new_regs_rect, false);
6656 new_window_sp->SetDelegate(
6657 WindowDelegateSP(new RegistersWindowDelegate(m_debugger)));
6658 }
6659 touchwin(stdscr);
6660 }
6661 return MenuActionResult::Handled;
6662
6663 case eMenuID_ViewBreakpoints: {
6664 WindowSP main_window_sp = m_app.GetMainWindow();
6665 WindowSP threads_window_sp = main_window_sp->FindSubWindow("Threads");
6666 WindowSP breakpoints_window_sp =
6667 main_window_sp->FindSubWindow("Breakpoints");
6668 const Rect threads_bounds = threads_window_sp->GetBounds();
6669
6670 // If a breakpoints window already exists, remove it and give the area
6671 // it used to occupy to the threads window. If it doesn't exist, split
6672 // the threads window horizontally into two windows where the top window
6673 // is the threads window and the bottom window is a newly added
6674 // breakpoints window.
6675 if (breakpoints_window_sp) {
6676 threads_window_sp->Resize(threads_bounds.size.width,
6677 threads_bounds.size.height +
6678 breakpoints_window_sp->GetHeight());
6679 main_window_sp->RemoveSubWindow(breakpoints_window_sp.get());
6680 } else {
6681 Rect new_threads_bounds, breakpoints_bounds;
6682 threads_bounds.HorizontalSplitPercentage(0.70, new_threads_bounds,
6683 breakpoints_bounds);
6684 threads_window_sp->SetBounds(new_threads_bounds);
6685 breakpoints_window_sp = main_window_sp->CreateSubWindow(
6686 "Breakpoints", breakpoints_bounds, false);
6687 TreeDelegateSP breakpoints_delegate_sp(
6688 new BreakpointsTreeDelegate(m_debugger));
6689 breakpoints_window_sp->SetDelegate(WindowDelegateSP(
6690 new TreeWindowDelegate(m_debugger, breakpoints_delegate_sp)));
6691 }
6692 touchwin(stdscr);
6693 return MenuActionResult::Handled;
6694 }
6695
6696 case eMenuID_HelpGUIHelp:
6697 m_app.GetMainWindow()->CreateHelpSubwindow();
6698 return MenuActionResult::Handled;
6699
6700 default:
6701 break;
6702 }
6703
6704 return MenuActionResult::NotHandled;
6705 }
6706
6707 protected:
6708 Application &m_app;
6709 Debugger &m_debugger;
6710 };
6711
6712 class StatusBarWindowDelegate : public WindowDelegate {
6713 public:
StatusBarWindowDelegate(Debugger & debugger)6714 StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) {
6715 FormatEntity::Parse("Thread: ${thread.id%tid}", m_format);
6716 }
6717
6718 ~StatusBarWindowDelegate() override = default;
6719
WindowDelegateDraw(Window & window,bool force)6720 bool WindowDelegateDraw(Window &window, bool force) override {
6721 ExecutionContext exe_ctx =
6722 m_debugger.GetCommandInterpreter().GetExecutionContext();
6723 Process *process = exe_ctx.GetProcessPtr();
6724 Thread *thread = exe_ctx.GetThreadPtr();
6725 StackFrame *frame = exe_ctx.GetFramePtr();
6726 window.Erase();
6727 window.SetBackground(BlackOnWhite);
6728 window.MoveCursor(0, 0);
6729 if (process) {
6730 const StateType state = process->GetState();
6731 window.Printf("Process: %5" PRIu64 " %10s", process->GetID(),
6732 StateAsCString(state));
6733
6734 if (StateIsStoppedState(state, true)) {
6735 StreamString strm;
6736 if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx,
6737 nullptr, nullptr, false, false)) {
6738 window.MoveCursor(40, 0);
6739 window.PutCStringTruncated(1, strm.GetString().str().c_str());
6740 }
6741
6742 window.MoveCursor(60, 0);
6743 if (frame)
6744 window.Printf("Frame: %3u PC = 0x%16.16" PRIx64,
6745 frame->GetFrameIndex(),
6746 frame->GetFrameCodeAddress().GetOpcodeLoadAddress(
6747 exe_ctx.GetTargetPtr()));
6748 } else if (state == eStateExited) {
6749 const char *exit_desc = process->GetExitDescription();
6750 const int exit_status = process->GetExitStatus();
6751 if (exit_desc && exit_desc[0])
6752 window.Printf(" with status = %i (%s)", exit_status, exit_desc);
6753 else
6754 window.Printf(" with status = %i", exit_status);
6755 }
6756 }
6757 return true;
6758 }
6759
6760 protected:
6761 Debugger &m_debugger;
6762 FormatEntity::Entry m_format;
6763 };
6764
6765 class SourceFileWindowDelegate : public WindowDelegate {
6766 public:
SourceFileWindowDelegate(Debugger & debugger)6767 SourceFileWindowDelegate(Debugger &debugger)
6768 : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(),
6769 m_disassembly_scope(nullptr), m_disassembly_sp(), m_disassembly_range(),
6770 m_title(), m_tid(LLDB_INVALID_THREAD_ID), m_line_width(4),
6771 m_selected_line(0), m_pc_line(0), m_stop_id(0), m_frame_idx(UINT32_MAX),
6772 m_first_visible_line(0), m_first_visible_column(0), m_min_x(0),
6773 m_min_y(0), m_max_x(0), m_max_y(0) {}
6774
6775 ~SourceFileWindowDelegate() override = default;
6776
Update(const SymbolContext & sc)6777 void Update(const SymbolContext &sc) { m_sc = sc; }
6778
NumVisibleLines() const6779 uint32_t NumVisibleLines() const { return m_max_y - m_min_y; }
6780
WindowDelegateGetHelpText()6781 const char *WindowDelegateGetHelpText() override {
6782 return "Source/Disassembly window keyboard shortcuts:";
6783 }
6784
WindowDelegateGetKeyHelp()6785 KeyHelp *WindowDelegateGetKeyHelp() override {
6786 static curses::KeyHelp g_source_view_key_help[] = {
6787 {KEY_RETURN, "Run to selected line with one shot breakpoint"},
6788 {KEY_UP, "Select previous source line"},
6789 {KEY_DOWN, "Select next source line"},
6790 {KEY_LEFT, "Scroll to the left"},
6791 {KEY_RIGHT, "Scroll to the right"},
6792 {KEY_PPAGE, "Page up"},
6793 {KEY_NPAGE, "Page down"},
6794 {'b', "Set breakpoint on selected source/disassembly line"},
6795 {'c', "Continue process"},
6796 {'D', "Detach with process suspended"},
6797 {'h', "Show help dialog"},
6798 {'n', "Step over (source line)"},
6799 {'N', "Step over (single instruction)"},
6800 {'f', "Step out (finish)"},
6801 {'s', "Step in (source line)"},
6802 {'S', "Step in (single instruction)"},
6803 {'u', "Frame up"},
6804 {'d', "Frame down"},
6805 {',', "Page up"},
6806 {'.', "Page down"},
6807 {'\0', nullptr}};
6808 return g_source_view_key_help;
6809 }
6810
WindowDelegateDraw(Window & window,bool force)6811 bool WindowDelegateDraw(Window &window, bool force) override {
6812 ExecutionContext exe_ctx =
6813 m_debugger.GetCommandInterpreter().GetExecutionContext();
6814 Process *process = exe_ctx.GetProcessPtr();
6815 Thread *thread = nullptr;
6816
6817 bool update_location = false;
6818 if (process) {
6819 StateType state = process->GetState();
6820 if (StateIsStoppedState(state, true)) {
6821 // We are stopped, so it is ok to
6822 update_location = true;
6823 }
6824 }
6825
6826 m_min_x = 1;
6827 m_min_y = 2;
6828 m_max_x = window.GetMaxX() - 1;
6829 m_max_y = window.GetMaxY() - 1;
6830
6831 const uint32_t num_visible_lines = NumVisibleLines();
6832 StackFrameSP frame_sp;
6833 bool set_selected_line_to_pc = false;
6834
6835 if (update_location) {
6836 const bool process_alive = process ? process->IsAlive() : false;
6837 bool thread_changed = false;
6838 if (process_alive) {
6839 thread = exe_ctx.GetThreadPtr();
6840 if (thread) {
6841 frame_sp = thread->GetSelectedFrame();
6842 auto tid = thread->GetID();
6843 thread_changed = tid != m_tid;
6844 m_tid = tid;
6845 } else {
6846 if (m_tid != LLDB_INVALID_THREAD_ID) {
6847 thread_changed = true;
6848 m_tid = LLDB_INVALID_THREAD_ID;
6849 }
6850 }
6851 }
6852 const uint32_t stop_id = process ? process->GetStopID() : 0;
6853 const bool stop_id_changed = stop_id != m_stop_id;
6854 bool frame_changed = false;
6855 m_stop_id = stop_id;
6856 m_title.Clear();
6857 if (frame_sp) {
6858 m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything);
6859 if (m_sc.module_sp) {
6860 m_title.Printf(
6861 "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString());
6862 ConstString func_name = m_sc.GetFunctionName();
6863 if (func_name)
6864 m_title.Printf("`%s", func_name.GetCString());
6865 }
6866 const uint32_t frame_idx = frame_sp->GetFrameIndex();
6867 frame_changed = frame_idx != m_frame_idx;
6868 m_frame_idx = frame_idx;
6869 } else {
6870 m_sc.Clear(true);
6871 frame_changed = m_frame_idx != UINT32_MAX;
6872 m_frame_idx = UINT32_MAX;
6873 }
6874
6875 const bool context_changed =
6876 thread_changed || frame_changed || stop_id_changed;
6877
6878 if (process_alive) {
6879 if (m_sc.line_entry.IsValid()) {
6880 m_pc_line = m_sc.line_entry.line;
6881 if (m_pc_line != UINT32_MAX)
6882 --m_pc_line; // Convert to zero based line number...
6883 // Update the selected line if the stop ID changed...
6884 if (context_changed)
6885 m_selected_line = m_pc_line;
6886
6887 if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) {
6888 // Same file, nothing to do, we should either have the lines or
6889 // not (source file missing)
6890 if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) {
6891 if (m_selected_line >= m_first_visible_line + num_visible_lines)
6892 m_first_visible_line = m_selected_line - 10;
6893 } else {
6894 if (m_selected_line > 10)
6895 m_first_visible_line = m_selected_line - 10;
6896 else
6897 m_first_visible_line = 0;
6898 }
6899 } else {
6900 // File changed, set selected line to the line with the PC
6901 m_selected_line = m_pc_line;
6902 m_file_sp =
6903 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file);
6904 if (m_file_sp) {
6905 const size_t num_lines = m_file_sp->GetNumLines();
6906 m_line_width = 1;
6907 for (size_t n = num_lines; n >= 10; n = n / 10)
6908 ++m_line_width;
6909
6910 if (num_lines < num_visible_lines ||
6911 m_selected_line < num_visible_lines)
6912 m_first_visible_line = 0;
6913 else
6914 m_first_visible_line = m_selected_line - 10;
6915 }
6916 }
6917 } else {
6918 m_file_sp.reset();
6919 }
6920
6921 if (!m_file_sp || m_file_sp->GetNumLines() == 0) {
6922 // Show disassembly
6923 bool prefer_file_cache = false;
6924 if (m_sc.function) {
6925 if (m_disassembly_scope != m_sc.function) {
6926 m_disassembly_scope = m_sc.function;
6927 m_disassembly_sp = m_sc.function->GetInstructions(
6928 exe_ctx, nullptr, !prefer_file_cache);
6929 if (m_disassembly_sp) {
6930 set_selected_line_to_pc = true;
6931 m_disassembly_range = m_sc.function->GetAddressRange();
6932 } else {
6933 m_disassembly_range.Clear();
6934 }
6935 } else {
6936 set_selected_line_to_pc = context_changed;
6937 }
6938 } else if (m_sc.symbol) {
6939 if (m_disassembly_scope != m_sc.symbol) {
6940 m_disassembly_scope = m_sc.symbol;
6941 m_disassembly_sp = m_sc.symbol->GetInstructions(
6942 exe_ctx, nullptr, prefer_file_cache);
6943 if (m_disassembly_sp) {
6944 set_selected_line_to_pc = true;
6945 m_disassembly_range.GetBaseAddress() =
6946 m_sc.symbol->GetAddress();
6947 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize());
6948 } else {
6949 m_disassembly_range.Clear();
6950 }
6951 } else {
6952 set_selected_line_to_pc = context_changed;
6953 }
6954 }
6955 }
6956 } else {
6957 m_pc_line = UINT32_MAX;
6958 }
6959 }
6960
6961 const int window_width = window.GetWidth();
6962 window.Erase();
6963 window.DrawTitleBox("Sources");
6964 if (!m_title.GetString().empty()) {
6965 window.AttributeOn(A_REVERSE);
6966 window.MoveCursor(1, 1);
6967 window.PutChar(' ');
6968 window.PutCStringTruncated(1, m_title.GetString().str().c_str());
6969 int x = window.GetCursorX();
6970 if (x < window_width - 1) {
6971 window.Printf("%*s", window_width - x - 1, "");
6972 }
6973 window.AttributeOff(A_REVERSE);
6974 }
6975
6976 Target *target = exe_ctx.GetTargetPtr();
6977 const size_t num_source_lines = GetNumSourceLines();
6978 if (num_source_lines > 0) {
6979 // Display source
6980 BreakpointLines bp_lines;
6981 if (target) {
6982 BreakpointList &bp_list = target->GetBreakpointList();
6983 const size_t num_bps = bp_list.GetSize();
6984 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
6985 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
6986 const size_t num_bps_locs = bp_sp->GetNumLocations();
6987 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
6988 BreakpointLocationSP bp_loc_sp =
6989 bp_sp->GetLocationAtIndex(bp_loc_idx);
6990 LineEntry bp_loc_line_entry;
6991 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
6992 bp_loc_line_entry)) {
6993 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) {
6994 bp_lines.insert(bp_loc_line_entry.line);
6995 }
6996 }
6997 }
6998 }
6999 }
7000
7001 const attr_t selected_highlight_attr = A_REVERSE;
7002 const attr_t pc_highlight_attr = COLOR_PAIR(BlackOnBlue);
7003
7004 for (size_t i = 0; i < num_visible_lines; ++i) {
7005 const uint32_t curr_line = m_first_visible_line + i;
7006 if (curr_line < num_source_lines) {
7007 const int line_y = m_min_y + i;
7008 window.MoveCursor(1, line_y);
7009 const bool is_pc_line = curr_line == m_pc_line;
7010 const bool line_is_selected = m_selected_line == curr_line;
7011 // Highlight the line as the PC line first, then if the selected
7012 // line isn't the same as the PC line, highlight it differently
7013 attr_t highlight_attr = 0;
7014 attr_t bp_attr = 0;
7015 if (is_pc_line)
7016 highlight_attr = pc_highlight_attr;
7017 else if (line_is_selected)
7018 highlight_attr = selected_highlight_attr;
7019
7020 if (bp_lines.find(curr_line + 1) != bp_lines.end())
7021 bp_attr = COLOR_PAIR(BlackOnWhite);
7022
7023 if (bp_attr)
7024 window.AttributeOn(bp_attr);
7025
7026 window.Printf(" %*u ", m_line_width, curr_line + 1);
7027
7028 if (bp_attr)
7029 window.AttributeOff(bp_attr);
7030
7031 window.PutChar(ACS_VLINE);
7032 // Mark the line with the PC with a diamond
7033 if (is_pc_line)
7034 window.PutChar(ACS_DIAMOND);
7035 else
7036 window.PutChar(' ');
7037
7038 if (highlight_attr)
7039 window.AttributeOn(highlight_attr);
7040
7041 StreamString lineStream;
7042 m_file_sp->DisplaySourceLines(curr_line + 1, {}, 0, 0, &lineStream);
7043 StringRef line = lineStream.GetString();
7044 if (line.endswith("\n"))
7045 line = line.drop_back();
7046 bool wasWritten = window.OutputColoredStringTruncated(
7047 1, line, m_first_visible_column, line_is_selected);
7048 if (line_is_selected && !wasWritten) {
7049 // Draw an empty space to show the selected line if empty,
7050 // or draw '<' if nothing is visible because of scrolling too much
7051 // to the right.
7052 window.PutCStringTruncated(
7053 1, line.empty() && m_first_visible_column == 0 ? " " : "<");
7054 }
7055
7056 if (is_pc_line && frame_sp &&
7057 frame_sp->GetConcreteFrameIndex() == 0) {
7058 StopInfoSP stop_info_sp;
7059 if (thread)
7060 stop_info_sp = thread->GetStopInfo();
7061 if (stop_info_sp) {
7062 const char *stop_description = stop_info_sp->GetDescription();
7063 if (stop_description && stop_description[0]) {
7064 size_t stop_description_len = strlen(stop_description);
7065 int desc_x = window_width - stop_description_len - 16;
7066 if (desc_x - window.GetCursorX() > 0)
7067 window.Printf("%*s", desc_x - window.GetCursorX(), "");
7068 window.MoveCursor(window_width - stop_description_len - 16,
7069 line_y);
7070 const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue);
7071 window.AttributeOn(stop_reason_attr);
7072 window.PrintfTruncated(1, " <<< Thread %u: %s ",
7073 thread->GetIndexID(), stop_description);
7074 window.AttributeOff(stop_reason_attr);
7075 }
7076 } else {
7077 window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
7078 }
7079 }
7080 if (highlight_attr)
7081 window.AttributeOff(highlight_attr);
7082 } else {
7083 break;
7084 }
7085 }
7086 } else {
7087 size_t num_disassembly_lines = GetNumDisassemblyLines();
7088 if (num_disassembly_lines > 0) {
7089 // Display disassembly
7090 BreakpointAddrs bp_file_addrs;
7091 Target *target = exe_ctx.GetTargetPtr();
7092 if (target) {
7093 BreakpointList &bp_list = target->GetBreakpointList();
7094 const size_t num_bps = bp_list.GetSize();
7095 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7096 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7097 const size_t num_bps_locs = bp_sp->GetNumLocations();
7098 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs;
7099 ++bp_loc_idx) {
7100 BreakpointLocationSP bp_loc_sp =
7101 bp_sp->GetLocationAtIndex(bp_loc_idx);
7102 LineEntry bp_loc_line_entry;
7103 const lldb::addr_t file_addr =
7104 bp_loc_sp->GetAddress().GetFileAddress();
7105 if (file_addr != LLDB_INVALID_ADDRESS) {
7106 if (m_disassembly_range.ContainsFileAddress(file_addr))
7107 bp_file_addrs.insert(file_addr);
7108 }
7109 }
7110 }
7111 }
7112
7113 const attr_t selected_highlight_attr = A_REVERSE;
7114 const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue);
7115
7116 StreamString strm;
7117
7118 InstructionList &insts = m_disassembly_sp->GetInstructionList();
7119 Address pc_address;
7120
7121 if (frame_sp)
7122 pc_address = frame_sp->GetFrameCodeAddress();
7123 const uint32_t pc_idx =
7124 pc_address.IsValid()
7125 ? insts.GetIndexOfInstructionAtAddress(pc_address)
7126 : UINT32_MAX;
7127 if (set_selected_line_to_pc) {
7128 m_selected_line = pc_idx;
7129 }
7130
7131 const uint32_t non_visible_pc_offset = (num_visible_lines / 5);
7132 if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines)
7133 m_first_visible_line = 0;
7134
7135 if (pc_idx < num_disassembly_lines) {
7136 if (pc_idx < static_cast<uint32_t>(m_first_visible_line) ||
7137 pc_idx >= m_first_visible_line + num_visible_lines)
7138 m_first_visible_line = pc_idx - non_visible_pc_offset;
7139 }
7140
7141 for (size_t i = 0; i < num_visible_lines; ++i) {
7142 const uint32_t inst_idx = m_first_visible_line + i;
7143 Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get();
7144 if (!inst)
7145 break;
7146
7147 const int line_y = m_min_y + i;
7148 window.MoveCursor(1, line_y);
7149 const bool is_pc_line = frame_sp && inst_idx == pc_idx;
7150 const bool line_is_selected = m_selected_line == inst_idx;
7151 // Highlight the line as the PC line first, then if the selected
7152 // line isn't the same as the PC line, highlight it differently
7153 attr_t highlight_attr = 0;
7154 attr_t bp_attr = 0;
7155 if (is_pc_line)
7156 highlight_attr = pc_highlight_attr;
7157 else if (line_is_selected)
7158 highlight_attr = selected_highlight_attr;
7159
7160 if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) !=
7161 bp_file_addrs.end())
7162 bp_attr = COLOR_PAIR(BlackOnWhite);
7163
7164 if (bp_attr)
7165 window.AttributeOn(bp_attr);
7166
7167 window.Printf(" 0x%16.16llx ",
7168 static_cast<unsigned long long>(
7169 inst->GetAddress().GetLoadAddress(target)));
7170
7171 if (bp_attr)
7172 window.AttributeOff(bp_attr);
7173
7174 window.PutChar(ACS_VLINE);
7175 // Mark the line with the PC with a diamond
7176 if (is_pc_line)
7177 window.PutChar(ACS_DIAMOND);
7178 else
7179 window.PutChar(' ');
7180
7181 if (highlight_attr)
7182 window.AttributeOn(highlight_attr);
7183
7184 const char *mnemonic = inst->GetMnemonic(&exe_ctx);
7185 const char *operands = inst->GetOperands(&exe_ctx);
7186 const char *comment = inst->GetComment(&exe_ctx);
7187
7188 if (mnemonic != nullptr && mnemonic[0] == '\0')
7189 mnemonic = nullptr;
7190 if (operands != nullptr && operands[0] == '\0')
7191 operands = nullptr;
7192 if (comment != nullptr && comment[0] == '\0')
7193 comment = nullptr;
7194
7195 strm.Clear();
7196
7197 if (mnemonic != nullptr && operands != nullptr && comment != nullptr)
7198 strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment);
7199 else if (mnemonic != nullptr && operands != nullptr)
7200 strm.Printf("%-8s %s", mnemonic, operands);
7201 else if (mnemonic != nullptr)
7202 strm.Printf("%s", mnemonic);
7203
7204 int right_pad = 1;
7205 window.PutCStringTruncated(
7206 right_pad,
7207 strm.GetString().substr(m_first_visible_column).data());
7208
7209 if (is_pc_line && frame_sp &&
7210 frame_sp->GetConcreteFrameIndex() == 0) {
7211 StopInfoSP stop_info_sp;
7212 if (thread)
7213 stop_info_sp = thread->GetStopInfo();
7214 if (stop_info_sp) {
7215 const char *stop_description = stop_info_sp->GetDescription();
7216 if (stop_description && stop_description[0]) {
7217 size_t stop_description_len = strlen(stop_description);
7218 int desc_x = window_width - stop_description_len - 16;
7219 if (desc_x - window.GetCursorX() > 0)
7220 window.Printf("%*s", desc_x - window.GetCursorX(), "");
7221 window.MoveCursor(window_width - stop_description_len - 15,
7222 line_y);
7223 window.PrintfTruncated(1, "<<< Thread %u: %s ",
7224 thread->GetIndexID(), stop_description);
7225 }
7226 } else {
7227 window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
7228 }
7229 }
7230 if (highlight_attr)
7231 window.AttributeOff(highlight_attr);
7232 }
7233 }
7234 }
7235 return true; // Drawing handled
7236 }
7237
GetNumLines()7238 size_t GetNumLines() {
7239 size_t num_lines = GetNumSourceLines();
7240 if (num_lines == 0)
7241 num_lines = GetNumDisassemblyLines();
7242 return num_lines;
7243 }
7244
GetNumSourceLines() const7245 size_t GetNumSourceLines() const {
7246 if (m_file_sp)
7247 return m_file_sp->GetNumLines();
7248 return 0;
7249 }
7250
GetNumDisassemblyLines() const7251 size_t GetNumDisassemblyLines() const {
7252 if (m_disassembly_sp)
7253 return m_disassembly_sp->GetInstructionList().GetSize();
7254 return 0;
7255 }
7256
WindowDelegateHandleChar(Window & window,int c)7257 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
7258 const uint32_t num_visible_lines = NumVisibleLines();
7259 const size_t num_lines = GetNumLines();
7260
7261 switch (c) {
7262 case ',':
7263 case KEY_PPAGE:
7264 // Page up key
7265 if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines)
7266 m_first_visible_line -= num_visible_lines;
7267 else
7268 m_first_visible_line = 0;
7269 m_selected_line = m_first_visible_line;
7270 return eKeyHandled;
7271
7272 case '.':
7273 case KEY_NPAGE:
7274 // Page down key
7275 {
7276 if (m_first_visible_line + num_visible_lines < num_lines)
7277 m_first_visible_line += num_visible_lines;
7278 else if (num_lines < num_visible_lines)
7279 m_first_visible_line = 0;
7280 else
7281 m_first_visible_line = num_lines - num_visible_lines;
7282 m_selected_line = m_first_visible_line;
7283 }
7284 return eKeyHandled;
7285
7286 case KEY_UP:
7287 if (m_selected_line > 0) {
7288 m_selected_line--;
7289 if (static_cast<size_t>(m_first_visible_line) > m_selected_line)
7290 m_first_visible_line = m_selected_line;
7291 }
7292 return eKeyHandled;
7293
7294 case KEY_DOWN:
7295 if (m_selected_line + 1 < num_lines) {
7296 m_selected_line++;
7297 if (m_first_visible_line + num_visible_lines < m_selected_line)
7298 m_first_visible_line++;
7299 }
7300 return eKeyHandled;
7301
7302 case KEY_LEFT:
7303 if (m_first_visible_column > 0)
7304 --m_first_visible_column;
7305 return eKeyHandled;
7306
7307 case KEY_RIGHT:
7308 ++m_first_visible_column;
7309 return eKeyHandled;
7310
7311 case '\r':
7312 case '\n':
7313 case KEY_ENTER:
7314 // Set a breakpoint and run to the line using a one shot breakpoint
7315 if (GetNumSourceLines() > 0) {
7316 ExecutionContext exe_ctx =
7317 m_debugger.GetCommandInterpreter().GetExecutionContext();
7318 if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) {
7319 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
7320 nullptr, // Don't limit the breakpoint to certain modules
7321 m_file_sp->GetFileSpec(), // Source file
7322 m_selected_line +
7323 1, // Source line number (m_selected_line is zero based)
7324 0, // Unspecified column.
7325 0, // No offset
7326 eLazyBoolCalculate, // Check inlines using global setting
7327 eLazyBoolCalculate, // Skip prologue using global setting,
7328 false, // internal
7329 false, // request_hardware
7330 eLazyBoolCalculate); // move_to_nearest_code
7331 // Make breakpoint one shot
7332 bp_sp->GetOptions().SetOneShot(true);
7333 exe_ctx.GetProcessRef().Resume();
7334 }
7335 } else if (m_selected_line < GetNumDisassemblyLines()) {
7336 const Instruction *inst = m_disassembly_sp->GetInstructionList()
7337 .GetInstructionAtIndex(m_selected_line)
7338 .get();
7339 ExecutionContext exe_ctx =
7340 m_debugger.GetCommandInterpreter().GetExecutionContext();
7341 if (exe_ctx.HasTargetScope()) {
7342 Address addr = inst->GetAddress();
7343 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
7344 addr, // lldb_private::Address
7345 false, // internal
7346 false); // request_hardware
7347 // Make breakpoint one shot
7348 bp_sp->GetOptions().SetOneShot(true);
7349 exe_ctx.GetProcessRef().Resume();
7350 }
7351 }
7352 return eKeyHandled;
7353
7354 case 'b': // 'b' == toggle breakpoint on currently selected line
7355 ToggleBreakpointOnSelectedLine();
7356 return eKeyHandled;
7357
7358 case 'D': // 'D' == detach and keep stopped
7359 {
7360 ExecutionContext exe_ctx =
7361 m_debugger.GetCommandInterpreter().GetExecutionContext();
7362 if (exe_ctx.HasProcessScope())
7363 exe_ctx.GetProcessRef().Detach(true);
7364 }
7365 return eKeyHandled;
7366
7367 case 'c':
7368 // 'c' == continue
7369 {
7370 ExecutionContext exe_ctx =
7371 m_debugger.GetCommandInterpreter().GetExecutionContext();
7372 if (exe_ctx.HasProcessScope())
7373 exe_ctx.GetProcessRef().Resume();
7374 }
7375 return eKeyHandled;
7376
7377 case 'f':
7378 // 'f' == step out (finish)
7379 {
7380 ExecutionContext exe_ctx =
7381 m_debugger.GetCommandInterpreter().GetExecutionContext();
7382 if (exe_ctx.HasThreadScope() &&
7383 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
7384 exe_ctx.GetThreadRef().StepOut();
7385 }
7386 }
7387 return eKeyHandled;
7388
7389 case 'n': // 'n' == step over
7390 case 'N': // 'N' == step over instruction
7391 {
7392 ExecutionContext exe_ctx =
7393 m_debugger.GetCommandInterpreter().GetExecutionContext();
7394 if (exe_ctx.HasThreadScope() &&
7395 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
7396 bool source_step = (c == 'n');
7397 exe_ctx.GetThreadRef().StepOver(source_step);
7398 }
7399 }
7400 return eKeyHandled;
7401
7402 case 's': // 's' == step into
7403 case 'S': // 'S' == step into instruction
7404 {
7405 ExecutionContext exe_ctx =
7406 m_debugger.GetCommandInterpreter().GetExecutionContext();
7407 if (exe_ctx.HasThreadScope() &&
7408 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
7409 bool source_step = (c == 's');
7410 exe_ctx.GetThreadRef().StepIn(source_step);
7411 }
7412 }
7413 return eKeyHandled;
7414
7415 case 'u': // 'u' == frame up
7416 case 'd': // 'd' == frame down
7417 {
7418 ExecutionContext exe_ctx =
7419 m_debugger.GetCommandInterpreter().GetExecutionContext();
7420 if (exe_ctx.HasThreadScope()) {
7421 Thread *thread = exe_ctx.GetThreadPtr();
7422 uint32_t frame_idx = thread->GetSelectedFrameIndex();
7423 if (frame_idx == UINT32_MAX)
7424 frame_idx = 0;
7425 if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount())
7426 ++frame_idx;
7427 else if (c == 'd' && frame_idx > 0)
7428 --frame_idx;
7429 if (thread->SetSelectedFrameByIndex(frame_idx, true))
7430 exe_ctx.SetFrameSP(thread->GetSelectedFrame());
7431 }
7432 }
7433 return eKeyHandled;
7434
7435 case 'h':
7436 window.CreateHelpSubwindow();
7437 return eKeyHandled;
7438
7439 default:
7440 break;
7441 }
7442 return eKeyNotHandled;
7443 }
7444
ToggleBreakpointOnSelectedLine()7445 void ToggleBreakpointOnSelectedLine() {
7446 ExecutionContext exe_ctx =
7447 m_debugger.GetCommandInterpreter().GetExecutionContext();
7448 if (!exe_ctx.HasTargetScope())
7449 return;
7450 if (GetNumSourceLines() > 0) {
7451 // Source file breakpoint.
7452 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
7453 const size_t num_bps = bp_list.GetSize();
7454 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7455 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7456 const size_t num_bps_locs = bp_sp->GetNumLocations();
7457 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
7458 BreakpointLocationSP bp_loc_sp =
7459 bp_sp->GetLocationAtIndex(bp_loc_idx);
7460 LineEntry bp_loc_line_entry;
7461 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
7462 bp_loc_line_entry)) {
7463 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file &&
7464 m_selected_line + 1 == bp_loc_line_entry.line) {
7465 bool removed =
7466 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
7467 assert(removed);
7468 UNUSED_IF_ASSERT_DISABLED(removed);
7469 return; // Existing breakpoint removed.
7470 }
7471 }
7472 }
7473 }
7474 // No breakpoint found on the location, add it.
7475 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
7476 nullptr, // Don't limit the breakpoint to certain modules
7477 m_file_sp->GetFileSpec(), // Source file
7478 m_selected_line +
7479 1, // Source line number (m_selected_line is zero based)
7480 0, // No column specified.
7481 0, // No offset
7482 eLazyBoolCalculate, // Check inlines using global setting
7483 eLazyBoolCalculate, // Skip prologue using global setting,
7484 false, // internal
7485 false, // request_hardware
7486 eLazyBoolCalculate); // move_to_nearest_code
7487 } else {
7488 // Disassembly breakpoint.
7489 assert(GetNumDisassemblyLines() > 0);
7490 assert(m_selected_line < GetNumDisassemblyLines());
7491 const Instruction *inst = m_disassembly_sp->GetInstructionList()
7492 .GetInstructionAtIndex(m_selected_line)
7493 .get();
7494 Address addr = inst->GetAddress();
7495 // Try to find it.
7496 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
7497 const size_t num_bps = bp_list.GetSize();
7498 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7499 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7500 const size_t num_bps_locs = bp_sp->GetNumLocations();
7501 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
7502 BreakpointLocationSP bp_loc_sp =
7503 bp_sp->GetLocationAtIndex(bp_loc_idx);
7504 LineEntry bp_loc_line_entry;
7505 const lldb::addr_t file_addr =
7506 bp_loc_sp->GetAddress().GetFileAddress();
7507 if (file_addr == addr.GetFileAddress()) {
7508 bool removed =
7509 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
7510 assert(removed);
7511 UNUSED_IF_ASSERT_DISABLED(removed);
7512 return; // Existing breakpoint removed.
7513 }
7514 }
7515 }
7516 // No breakpoint found on the address, add it.
7517 BreakpointSP bp_sp =
7518 exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address
7519 false, // internal
7520 false); // request_hardware
7521 }
7522 }
7523
7524 protected:
7525 typedef std::set<uint32_t> BreakpointLines;
7526 typedef std::set<lldb::addr_t> BreakpointAddrs;
7527
7528 Debugger &m_debugger;
7529 SymbolContext m_sc;
7530 SourceManager::FileSP m_file_sp;
7531 SymbolContextScope *m_disassembly_scope;
7532 lldb::DisassemblerSP m_disassembly_sp;
7533 AddressRange m_disassembly_range;
7534 StreamString m_title;
7535 lldb::user_id_t m_tid;
7536 int m_line_width;
7537 uint32_t m_selected_line; // The selected line
7538 uint32_t m_pc_line; // The line with the PC
7539 uint32_t m_stop_id;
7540 uint32_t m_frame_idx;
7541 int m_first_visible_line;
7542 int m_first_visible_column;
7543 int m_min_x;
7544 int m_min_y;
7545 int m_max_x;
7546 int m_max_y;
7547 };
7548
7549 DisplayOptions ValueObjectListDelegate::g_options = {true};
7550
IOHandlerCursesGUI(Debugger & debugger)7551 IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger)
7552 : IOHandler(debugger, IOHandler::Type::Curses) {}
7553
Activate()7554 void IOHandlerCursesGUI::Activate() {
7555 IOHandler::Activate();
7556 if (!m_app_ap) {
7557 m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE());
7558
7559 // This is both a window and a menu delegate
7560 std::shared_ptr<ApplicationDelegate> app_delegate_sp(
7561 new ApplicationDelegate(*m_app_ap, m_debugger));
7562
7563 MenuDelegateSP app_menu_delegate_sp =
7564 std::static_pointer_cast<MenuDelegate>(app_delegate_sp);
7565 MenuSP lldb_menu_sp(
7566 new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB));
7567 MenuSP exit_menuitem_sp(
7568 new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit));
7569 exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit);
7570 lldb_menu_sp->AddSubmenu(MenuSP(new Menu(
7571 "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout)));
7572 lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
7573 lldb_menu_sp->AddSubmenu(exit_menuitem_sp);
7574
7575 MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2),
7576 ApplicationDelegate::eMenuID_Target));
7577 target_menu_sp->AddSubmenu(MenuSP(new Menu(
7578 "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate)));
7579 target_menu_sp->AddSubmenu(MenuSP(new Menu(
7580 "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete)));
7581
7582 MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3),
7583 ApplicationDelegate::eMenuID_Process));
7584 process_menu_sp->AddSubmenu(MenuSP(new Menu(
7585 "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach)));
7586 process_menu_sp->AddSubmenu(
7587 MenuSP(new Menu("Detach and resume", nullptr, 'd',
7588 ApplicationDelegate::eMenuID_ProcessDetachResume)));
7589 process_menu_sp->AddSubmenu(
7590 MenuSP(new Menu("Detach suspended", nullptr, 's',
7591 ApplicationDelegate::eMenuID_ProcessDetachSuspended)));
7592 process_menu_sp->AddSubmenu(MenuSP(new Menu(
7593 "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch)));
7594 process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
7595 process_menu_sp->AddSubmenu(
7596 MenuSP(new Menu("Continue", nullptr, 'c',
7597 ApplicationDelegate::eMenuID_ProcessContinue)));
7598 process_menu_sp->AddSubmenu(MenuSP(new Menu(
7599 "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt)));
7600 process_menu_sp->AddSubmenu(MenuSP(new Menu(
7601 "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill)));
7602
7603 MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4),
7604 ApplicationDelegate::eMenuID_Thread));
7605 thread_menu_sp->AddSubmenu(MenuSP(new Menu(
7606 "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn)));
7607 thread_menu_sp->AddSubmenu(
7608 MenuSP(new Menu("Step Over", nullptr, 'v',
7609 ApplicationDelegate::eMenuID_ThreadStepOver)));
7610 thread_menu_sp->AddSubmenu(MenuSP(new Menu(
7611 "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut)));
7612
7613 MenuSP view_menu_sp(
7614 new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View));
7615 view_menu_sp->AddSubmenu(
7616 MenuSP(new Menu("Backtrace", nullptr, 't',
7617 ApplicationDelegate::eMenuID_ViewBacktrace)));
7618 view_menu_sp->AddSubmenu(
7619 MenuSP(new Menu("Registers", nullptr, 'r',
7620 ApplicationDelegate::eMenuID_ViewRegisters)));
7621 view_menu_sp->AddSubmenu(MenuSP(new Menu(
7622 "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource)));
7623 view_menu_sp->AddSubmenu(
7624 MenuSP(new Menu("Variables", nullptr, 'v',
7625 ApplicationDelegate::eMenuID_ViewVariables)));
7626 view_menu_sp->AddSubmenu(
7627 MenuSP(new Menu("Breakpoints", nullptr, 'b',
7628 ApplicationDelegate::eMenuID_ViewBreakpoints)));
7629
7630 MenuSP help_menu_sp(
7631 new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help));
7632 help_menu_sp->AddSubmenu(MenuSP(new Menu(
7633 "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp)));
7634
7635 m_app_ap->Initialize();
7636 WindowSP &main_window_sp = m_app_ap->GetMainWindow();
7637
7638 MenuSP menubar_sp(new Menu(Menu::Type::Bar));
7639 menubar_sp->AddSubmenu(lldb_menu_sp);
7640 menubar_sp->AddSubmenu(target_menu_sp);
7641 menubar_sp->AddSubmenu(process_menu_sp);
7642 menubar_sp->AddSubmenu(thread_menu_sp);
7643 menubar_sp->AddSubmenu(view_menu_sp);
7644 menubar_sp->AddSubmenu(help_menu_sp);
7645 menubar_sp->SetDelegate(app_menu_delegate_sp);
7646
7647 Rect content_bounds = main_window_sp->GetFrame();
7648 Rect menubar_bounds = content_bounds.MakeMenuBar();
7649 Rect status_bounds = content_bounds.MakeStatusBar();
7650 Rect source_bounds;
7651 Rect variables_bounds;
7652 Rect threads_bounds;
7653 Rect source_variables_bounds;
7654 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
7655 threads_bounds);
7656 source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds,
7657 variables_bounds);
7658
7659 WindowSP menubar_window_sp =
7660 main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false);
7661 // Let the menubar get keys if the active window doesn't handle the keys
7662 // that are typed so it can respond to menubar key presses.
7663 menubar_window_sp->SetCanBeActive(
7664 false); // Don't let the menubar become the active window
7665 menubar_window_sp->SetDelegate(menubar_sp);
7666
7667 WindowSP source_window_sp(
7668 main_window_sp->CreateSubWindow("Source", source_bounds, true));
7669 WindowSP variables_window_sp(
7670 main_window_sp->CreateSubWindow("Variables", variables_bounds, false));
7671 WindowSP threads_window_sp(
7672 main_window_sp->CreateSubWindow("Threads", threads_bounds, false));
7673 WindowSP status_window_sp(
7674 main_window_sp->CreateSubWindow("Status", status_bounds, false));
7675 status_window_sp->SetCanBeActive(
7676 false); // Don't let the status bar become the active window
7677 main_window_sp->SetDelegate(
7678 std::static_pointer_cast<WindowDelegate>(app_delegate_sp));
7679 source_window_sp->SetDelegate(
7680 WindowDelegateSP(new SourceFileWindowDelegate(m_debugger)));
7681 variables_window_sp->SetDelegate(
7682 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
7683 TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger));
7684 threads_window_sp->SetDelegate(WindowDelegateSP(
7685 new TreeWindowDelegate(m_debugger, thread_delegate_sp)));
7686 status_window_sp->SetDelegate(
7687 WindowDelegateSP(new StatusBarWindowDelegate(m_debugger)));
7688
7689 // Show the main help window once the first time the curses GUI is
7690 // launched
7691 static bool g_showed_help = false;
7692 if (!g_showed_help) {
7693 g_showed_help = true;
7694 main_window_sp->CreateHelpSubwindow();
7695 }
7696
7697 // All colors with black background.
7698 init_pair(1, COLOR_BLACK, COLOR_BLACK);
7699 init_pair(2, COLOR_RED, COLOR_BLACK);
7700 init_pair(3, COLOR_GREEN, COLOR_BLACK);
7701 init_pair(4, COLOR_YELLOW, COLOR_BLACK);
7702 init_pair(5, COLOR_BLUE, COLOR_BLACK);
7703 init_pair(6, COLOR_MAGENTA, COLOR_BLACK);
7704 init_pair(7, COLOR_CYAN, COLOR_BLACK);
7705 init_pair(8, COLOR_WHITE, COLOR_BLACK);
7706 // All colors with blue background.
7707 init_pair(9, COLOR_BLACK, COLOR_BLUE);
7708 init_pair(10, COLOR_RED, COLOR_BLUE);
7709 init_pair(11, COLOR_GREEN, COLOR_BLUE);
7710 init_pair(12, COLOR_YELLOW, COLOR_BLUE);
7711 init_pair(13, COLOR_BLUE, COLOR_BLUE);
7712 init_pair(14, COLOR_MAGENTA, COLOR_BLUE);
7713 init_pair(15, COLOR_CYAN, COLOR_BLUE);
7714 init_pair(16, COLOR_WHITE, COLOR_BLUE);
7715 // These must match the order in the color indexes enum.
7716 init_pair(17, COLOR_BLACK, COLOR_WHITE);
7717 init_pair(18, COLOR_MAGENTA, COLOR_WHITE);
7718 static_assert(LastColorPairIndex == 18, "Color indexes do not match.");
7719
7720 define_key("\033[Z", KEY_SHIFT_TAB);
7721 define_key("\033\015", KEY_ALT_ENTER);
7722 }
7723 }
7724
Deactivate()7725 void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); }
7726
Run()7727 void IOHandlerCursesGUI::Run() {
7728 m_app_ap->Run(m_debugger);
7729 SetIsDone(true);
7730 }
7731
7732 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default;
7733
Cancel()7734 void IOHandlerCursesGUI::Cancel() {}
7735
Interrupt()7736 bool IOHandlerCursesGUI::Interrupt() { return false; }
7737
GotEOF()7738 void IOHandlerCursesGUI::GotEOF() {}
7739
TerminalSizeChanged()7740 void IOHandlerCursesGUI::TerminalSizeChanged() {
7741 m_app_ap->TerminalSizeChanged();
7742 }
7743
7744 #endif // LLDB_ENABLE_CURSES
7745