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