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