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

source code of lldb/source/Core/IOHandlerCursesGUI.cpp