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->IsExecutable())
2965 return "";
2966
2967 return module_sp->GetFileSpec().GetFilename().GetString();
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 llvm::StringRef target_settings_argv0 = target->GetArg0();
3448
3449 if (!target_settings_argv0.empty()) {
3450 launch_info.GetArguments().AppendArgument(target_settings_argv0);
3451 launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(),
3452 false);
3453 return;
3454 }
3455
3456 launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(),
3457 true);
3458 }
3459
3460 void GetArguments(ProcessLaunchInfo &launch_info) {
3461 TargetSP target = m_debugger
3462 .GetSelectedExecutionContext(
3463 /*adopt_dummy_target=*/false)
3464 .GetTargetSP();
3465 Args arguments = m_arguments_field->GetArguments();
3466 launch_info.GetArguments().AppendArguments(arguments);
3467 }
3468
3469 void GetEnvironment(ProcessLaunchInfo &launch_info) {
3470 Environment target_environment =
3471 m_target_environment_field->GetEnvironment();
3472 Environment inherited_environment =
3473 m_inherited_environment_field->GetEnvironment();
3474 launch_info.GetEnvironment().insert(target_environment.begin(),
3475 target_environment.end());
3476 launch_info.GetEnvironment().insert(inherited_environment.begin(),
3477 inherited_environment.end());
3478 }
3479
3480 void GetWorkingDirectory(ProcessLaunchInfo &launch_info) {
3481 if (m_working_directory_field->IsSpecified())
3482 launch_info.SetWorkingDirectory(
3483 m_working_directory_field->GetResolvedFileSpec());
3484 }
3485
3486 void GetStopAtEntry(ProcessLaunchInfo &launch_info) {
3487 if (m_stop_at_entry_field->GetBoolean())
3488 launch_info.GetFlags().Set(eLaunchFlagStopAtEntry);
3489 else
3490 launch_info.GetFlags().Clear(eLaunchFlagStopAtEntry);
3491 }
3492
3493 void GetDetachOnError(ProcessLaunchInfo &launch_info) {
3494 if (m_detach_on_error_field->GetBoolean())
3495 launch_info.GetFlags().Set(eLaunchFlagDetachOnError);
3496 else
3497 launch_info.GetFlags().Clear(eLaunchFlagDetachOnError);
3498 }
3499
3500 void GetDisableASLR(ProcessLaunchInfo &launch_info) {
3501 if (m_disable_aslr_field->GetBoolean())
3502 launch_info.GetFlags().Set(eLaunchFlagDisableASLR);
3503 else
3504 launch_info.GetFlags().Clear(eLaunchFlagDisableASLR);
3505 }
3506
3507 void GetPlugin(ProcessLaunchInfo &launch_info) {
3508 launch_info.SetProcessPluginName(m_plugin_field->GetPluginName());
3509 }
3510
3511 void GetArch(ProcessLaunchInfo &launch_info) {
3512 if (!m_arch_field->IsSpecified())
3513 return;
3514
3515 TargetSP target_sp = m_debugger
3516 .GetSelectedExecutionContext(
3517 /*adopt_dummy_target=*/false)
3518 .GetTargetSP();
3519 PlatformSP platform_sp =
3520 target_sp ? target_sp->GetPlatform() : PlatformSP();
3522 platform_sp.get(), m_arch_field->GetArchString());
3523 }
3524
3525 void GetShell(ProcessLaunchInfo &launch_info) {
3526 if (!m_shell_field->IsSpecified())
3527 return;
3528
3529 launch_info.SetShell(m_shell_field->GetResolvedFileSpec());
3530 launch_info.SetShellExpandArguments(
3531 m_expand_shell_arguments_field->GetBoolean());
3532 }
3533
3534 void GetStandardIO(ProcessLaunchInfo &launch_info) {
3535 if (m_disable_standard_io_field->GetBoolean()) {
3536 launch_info.GetFlags().Set(eLaunchFlagDisableSTDIO);
3537 return;
3538 }
3539
3540 FileAction action;
3541 if (m_standard_input_field->IsSpecified()) {
3542 if (action.Open(STDIN_FILENO, m_standard_input_field->GetFileSpec(), true,
3543 false))
3544 launch_info.AppendFileAction(action);
3545 }
3546 if (m_standard_output_field->IsSpecified()) {
3547 if (action.Open(STDOUT_FILENO, m_standard_output_field->GetFileSpec(),
3548 false, true))
3549 launch_info.AppendFileAction(action);
3550 }
3551 if (m_standard_error_field->IsSpecified()) {
3552 if (action.Open(STDERR_FILENO, m_standard_error_field->GetFileSpec(),
3553 false, true))
3554 launch_info.AppendFileAction(action);
3555 }
3556 }
3557
3558 void GetInheritTCC(ProcessLaunchInfo &launch_info) {
3559 if (Target *target = m_debugger
3560 .GetSelectedExecutionContext(
3561 /*adopt_dummy_target=*/false)
3562 .GetTargetPtr();
3563 target && target->GetInheritTCC())
3564 launch_info.GetFlags().Set(eLaunchFlagInheritTCCFromParent);
3565 }
3566
3567 ProcessLaunchInfo GetLaunchInfo() {
3568 ProcessLaunchInfo launch_info;
3569
3570 GetExecutableSettings(launch_info);
3571 GetArguments(launch_info);
3572 GetEnvironment(launch_info);
3573 GetWorkingDirectory(launch_info);
3574 GetStopAtEntry(launch_info);
3575 GetDetachOnError(launch_info);
3576 GetDisableASLR(launch_info);
3577 GetPlugin(launch_info);
3578 GetArch(launch_info);
3579 GetShell(launch_info);
3580 GetStandardIO(launch_info);
3581 GetInheritTCC(launch_info);
3582
3583 return launch_info;
3584 }
3585
3586 bool StopRunningProcess() {
3587 ExecutionContext exe_ctx =
3588 m_debugger.GetCommandInterpreter().GetExecutionContext();
3589
3590 if (!exe_ctx.HasProcessScope())
3591 return false;
3592
3593 Process *process = exe_ctx.GetProcessPtr();
3594 if (!(process && process->IsAlive()))
3595 return false;
3596
3597 FormDelegateSP form_delegate_sp =
3598 FormDelegateSP(new DetachOrKillProcessFormDelegate(process));
3599 Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);
3600 WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(
3601 form_delegate_sp->GetName().c_str(), bounds, true);
3602 WindowDelegateSP window_delegate_sp =
3603 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
3604 form_window_sp->SetDelegate(window_delegate_sp);
3605
3606 return true;
3607 }
3608
3609 Target *GetTarget() {
3610 Target *target = m_debugger
3611 .GetSelectedExecutionContext(
3612 /*adopt_dummy_target=*/false)
3613 .GetTargetPtr();
3614
3615 if (target == nullptr) {
3616 SetError("No target exists!");
3617 return nullptr;
3618 }
3619
3620 ModuleSP exe_module_sp = target->GetExecutableModule();
3621
3622 if (exe_module_sp == nullptr) {
3623 SetError("No executable in target!");
3624 return nullptr;
3625 }
3626
3627 return target;
3628 }
3629
3630 void Launch(Window &window) {
3631 ClearError();
3632
3633 bool all_fields_are_valid = CheckFieldsValidity();
3634 if (!all_fields_are_valid)
3635 return;
3636
3637 bool process_is_running = StopRunningProcess();
3638 if (process_is_running)
3639 return;
3640
3641 Target *target = GetTarget();
3642 if (HasError())
3643 return;
3644
3645 StreamString stream;
3646 ProcessLaunchInfo launch_info = GetLaunchInfo();
3647 Status status = target->Launch(launch_info, &stream);
3648
3649 if (status.Fail()) {
3650 SetError(status.AsCString());
3651 return;
3652 }
3653
3654 ProcessSP process_sp(target->GetProcessSP());
3655 if (!process_sp) {
3656 SetError("Launched successfully but target has no process!");
3657 return;
3658 }
3659
3660 window.GetParent()->RemoveSubWindow(&window);
3661 }
3662
3663protected:
3664 Debugger &m_debugger;
3665 WindowSP m_main_window_sp;
3666
3667 ArgumentsFieldDelegate *m_arguments_field;
3668 EnvironmentVariableListFieldDelegate *m_target_environment_field;
3669 DirectoryFieldDelegate *m_working_directory_field;
3670
3671 BooleanFieldDelegate *m_show_advanced_field;
3672
3673 BooleanFieldDelegate *m_stop_at_entry_field;
3674 BooleanFieldDelegate *m_detach_on_error_field;
3675 BooleanFieldDelegate *m_disable_aslr_field;
3676 ProcessPluginFieldDelegate *m_plugin_field;
3677 ArchFieldDelegate *m_arch_field;
3678 FileFieldDelegate *m_shell_field;
3679 BooleanFieldDelegate *m_expand_shell_arguments_field;
3680 BooleanFieldDelegate *m_disable_standard_io_field;
3681 FileFieldDelegate *m_standard_input_field;
3682 FileFieldDelegate *m_standard_output_field;
3683 FileFieldDelegate *m_standard_error_field;
3684
3685 BooleanFieldDelegate *m_show_inherited_environment_field;
3686 EnvironmentVariableListFieldDelegate *m_inherited_environment_field;
3687};
3688
3689////////////
3690// Searchers
3691////////////
3692
3693class SearcherDelegate {
3694public:
3695 SearcherDelegate() = default;
3696
3697 virtual ~SearcherDelegate() = default;
3698
3699 virtual int GetNumberOfMatches() = 0;
3700
3701 // Get the string that will be displayed for the match at the input index.
3702 virtual const std::string &GetMatchTextAtIndex(int index) = 0;
3703
3704 // Update the matches of the search. This is executed every time the text
3705 // field handles an event.
3706 virtual void UpdateMatches(const std::string &text) = 0;
3707
3708 // Execute the user callback given the index of some match. This is executed
3709 // once the user selects a match.
3710 virtual void ExecuteCallback(int match_index) = 0;
3711};
3712
3713typedef std::shared_ptr<SearcherDelegate> SearcherDelegateSP;
3714
3715class SearcherWindowDelegate : public WindowDelegate {
3716public:
3717 SearcherWindowDelegate(SearcherDelegateSP &delegate_sp)
3718 : m_delegate_sp(delegate_sp), m_text_field("Search", "", false) {
3719 ;
3720 }
3721
3722 // A completion window is padded by one character from all sides. A text field
3723 // is first drawn for inputting the searcher request, then a list of matches
3724 // are displayed in a scrollable list.
3725 //
3726 // ___<Searcher Window Name>____________________________
3727 // | |
3728 // | __[Search]_______________________________________ |
3729 // | | | |
3730 // | |_______________________________________________| |
3731 // | - Match 1. |
3732 // | - Match 2. |
3733 // | - ... |
3734 // | |
3735 // |____________________________[Press Esc to Cancel]__|
3736 //
3737
3738 // Get the index of the last visible match. Assuming at least one match
3739 // exists.
3740 int GetLastVisibleMatch(int height) {
3741 int index = m_first_visible_match + height;
3742 return std::min(index, m_delegate_sp->GetNumberOfMatches()) - 1;
3743 }
3744
3745 int GetNumberOfVisibleMatches(int height) {
3746 return GetLastVisibleMatch(height) - m_first_visible_match + 1;
3747 }
3748
3749 void UpdateScrolling(Surface &surface) {
3750 if (m_selected_match < m_first_visible_match) {
3751 m_first_visible_match = m_selected_match;
3752 return;
3753 }
3754
3755 int height = surface.GetHeight();
3756 int last_visible_match = GetLastVisibleMatch(height);
3757 if (m_selected_match > last_visible_match) {
3758 m_first_visible_match = m_selected_match - height + 1;
3759 }
3760 }
3761
3762 void DrawMatches(Surface &surface) {
3763 if (m_delegate_sp->GetNumberOfMatches() == 0)
3764 return;
3765
3766 UpdateScrolling(surface);
3767
3768 int count = GetNumberOfVisibleMatches(surface.GetHeight());
3769 for (int i = 0; i < count; i++) {
3770 surface.MoveCursor(1, i);
3771 int current_match = m_first_visible_match + i;
3772 if (current_match == m_selected_match)
3773 surface.AttributeOn(A_REVERSE);
3774 surface.PutCString(
3775 m_delegate_sp->GetMatchTextAtIndex(current_match).c_str());
3776 if (current_match == m_selected_match)
3777 surface.AttributeOff(A_REVERSE);
3778 }
3779 }
3780
3781 void DrawContent(Surface &surface) {
3782 Rect content_bounds = surface.GetFrame();
3783 Rect text_field_bounds, matchs_bounds;
3784 content_bounds.HorizontalSplit(m_text_field.FieldDelegateGetHeight(),
3785 text_field_bounds, matchs_bounds);
3786 Surface text_field_surface = surface.SubSurface(text_field_bounds);
3787 Surface matches_surface = surface.SubSurface(matchs_bounds);
3788
3789 m_text_field.FieldDelegateDraw(text_field_surface, true);
3790 DrawMatches(matches_surface);
3791 }
3792
3793 bool WindowDelegateDraw(Window &window, bool force) override {
3794 window.Erase();
3795
3796 window.DrawTitleBox(window.GetName(), "Press Esc to Cancel");
3797
3798 Rect content_bounds = window.GetFrame();
3799 content_bounds.Inset(2, 2);
3800 Surface content_surface = window.SubSurface(content_bounds);
3801
3802 DrawContent(content_surface);
3803 return true;
3804 }
3805
3806 void SelectNext() {
3807 if (m_selected_match != m_delegate_sp->GetNumberOfMatches() - 1)
3808 m_selected_match++;
3809 }
3810
3811 void SelectPrevious() {
3812 if (m_selected_match != 0)
3813 m_selected_match--;
3814 }
3815
3816 void ExecuteCallback(Window &window) {
3817 m_delegate_sp->ExecuteCallback(m_selected_match);
3818 window.GetParent()->RemoveSubWindow(&window);
3819 }
3820
3821 void UpdateMatches() {
3822 m_delegate_sp->UpdateMatches(m_text_field.GetText());
3823 m_selected_match = 0;
3824 }
3825
3826 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
3827 switch (key) {
3828 case '\r':
3829 case '\n':
3830 case KEY_ENTER:
3831 ExecuteCallback(window);
3832 return eKeyHandled;
3833 case '\t':
3834 case KEY_DOWN:
3835 SelectNext();
3836 return eKeyHandled;
3837 case KEY_SHIFT_TAB:
3838 case KEY_UP:
3839 SelectPrevious();
3840 return eKeyHandled;
3841 case KEY_ESCAPE:
3842 window.GetParent()->RemoveSubWindow(&window);
3843 return eKeyHandled;
3844 default:
3845 break;
3846 }
3847
3848 if (m_text_field.FieldDelegateHandleChar(key) == eKeyHandled)
3849 UpdateMatches();
3850
3851 return eKeyHandled;
3852 }
3853
3854protected:
3855 SearcherDelegateSP m_delegate_sp;
3856 TextFieldDelegate m_text_field;
3857 // The index of the currently selected match.
3858 int m_selected_match = 0;
3859 // The index of the first visible match.
3860 int m_first_visible_match = 0;
3861};
3862
3863//////////////////////////////
3864// Searcher Delegate Instances
3865//////////////////////////////
3866
3867// This is a searcher delegate wrapper around CommandCompletions common
3868// callbacks. The callbacks are only given the match string. The completion_mask
3869// can be a combination of lldb::CompletionType.
3870class CommonCompletionSearcherDelegate : public SearcherDelegate {
3871public:
3872 typedef std::function<void(const std::string &)> CallbackType;
3873
3874 CommonCompletionSearcherDelegate(Debugger &debugger, uint32_t completion_mask,
3875 CallbackType callback)
3876 : m_debugger(debugger), m_completion_mask(completion_mask),
3877 m_callback(callback) {}
3878
3879 int GetNumberOfMatches() override { return m_matches.GetSize(); }
3880
3881 const std::string &GetMatchTextAtIndex(int index) override {
3882 return m_matches[index];
3883 }
3884
3885 void UpdateMatches(const std::string &text) override {
3886 CompletionResult result;
3887 CompletionRequest request(text.c_str(), text.size(), result);
3889 m_debugger.GetCommandInterpreter(), m_completion_mask, request,
3890 nullptr);
3891 result.GetMatches(m_matches);
3892 }
3893
3894 void ExecuteCallback(int match_index) override {
3895 m_callback(m_matches[match_index]);
3896 }
3897
3898protected:
3899 Debugger &m_debugger;
3900 // A compound mask from lldb::CompletionType.
3901 uint32_t m_completion_mask;
3902 // A callback to execute once the user selects a match. The match is passed to
3903 // the callback as a string.
3904 CallbackType m_callback;
3905 StringList m_matches;
3906};
3907
3908////////
3909// Menus
3910////////
3911
3912class MenuDelegate {
3913public:
3914 virtual ~MenuDelegate() = default;
3915
3916 virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0;
3917};
3918
3919class Menu : public WindowDelegate {
3920public:
3921 enum class Type { Invalid, Bar, Item, Separator };
3922
3923 // Menubar or separator constructor
3924 Menu(Type type);
3925
3926 // Menuitem constructor
3927 Menu(const char *name, const char *key_name, int key_value,
3928 uint64_t identifier);
3929
3930 ~Menu() override = default;
3931
3932 const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; }
3933
3934 void SetDelegate(const MenuDelegateSP &delegate_sp) {
3935 m_delegate_sp = delegate_sp;
3936 }
3937
3938 void RecalculateNameLengths();
3939
3940 void AddSubmenu(const MenuSP &menu_sp);
3941
3942 int DrawAndRunMenu(Window &window);
3943
3944 void DrawMenuTitle(Window &window, bool highlight);
3945
3946 bool WindowDelegateDraw(Window &window, bool force) override;
3947
3948 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
3949
3950 MenuActionResult ActionPrivate(Menu &menu) {
3951 MenuActionResult result = MenuActionResult::NotHandled;
3952 if (m_delegate_sp) {
3953 result = m_delegate_sp->MenuDelegateAction(menu);
3954 if (result != MenuActionResult::NotHandled)
3955 return result;
3956 } else if (m_parent) {
3957 result = m_parent->ActionPrivate(menu);
3958 if (result != MenuActionResult::NotHandled)
3959 return result;
3960 }
3961 return m_canned_result;
3962 }
3963
3964 MenuActionResult Action() {
3965 // Call the recursive action so it can try to handle it with the menu
3966 // delegate, and if not, try our parent menu
3967 return ActionPrivate(*this);
3968 }
3969
3970 void SetCannedResult(MenuActionResult result) { m_canned_result = result; }
3971
3972 Menus &GetSubmenus() { return m_submenus; }
3973
3974 const Menus &GetSubmenus() const { return m_submenus; }
3975
3976 int GetSelectedSubmenuIndex() const { return m_selected; }
3977
3978 void SetSelectedSubmenuIndex(int idx) { m_selected = idx; }
3979
3980 Type GetType() const { return m_type; }
3981
3982 int GetStartingColumn() const { return m_start_col; }
3983
3984 void SetStartingColumn(int col) { m_start_col = col; }
3985
3986 int GetKeyValue() const { return m_key_value; }
3987
3988 std::string &GetName() { return m_name; }
3989
3990 int GetDrawWidth() const {
3991 return m_max_submenu_name_length + m_max_submenu_key_name_length + 8;
3992 }
3993
3994 uint64_t GetIdentifier() const { return m_identifier; }
3995
3996 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
3997
3998protected:
3999 std::string m_name;
4000 std::string m_key_name;
4001 uint64_t m_identifier;
4002 Type m_type;
4003 int m_key_value;
4004 int m_start_col;
4005 int m_max_submenu_name_length;
4006 int m_max_submenu_key_name_length;
4007 int m_selected;
4008 Menu *m_parent;
4009 Menus m_submenus;
4010 WindowSP m_menu_window_sp;
4011 MenuActionResult m_canned_result;
4012 MenuDelegateSP m_delegate_sp;
4013};
4014
4015// Menubar or separator constructor
4016Menu::Menu(Type type)
4017 : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0),
4018 m_start_col(0), m_max_submenu_name_length(0),
4019 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
4020 m_submenus(), m_canned_result(MenuActionResult::NotHandled),
4021 m_delegate_sp() {}
4022
4023// Menuitem constructor
4024Menu::Menu(const char *name, const char *key_name, int key_value,
4025 uint64_t identifier)
4026 : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid),
4027 m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0),
4028 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
4029 m_submenus(), m_canned_result(MenuActionResult::NotHandled),
4030 m_delegate_sp() {
4031 if (name && name[0]) {
4032 m_name = name;
4033 m_type = Type::Item;
4034 if (key_name && key_name[0])
4035 m_key_name = key_name;
4036 } else {
4037 m_type = Type::Separator;
4038 }
4039}
4040
4041void Menu::RecalculateNameLengths() {
4042 m_max_submenu_name_length = 0;
4043 m_max_submenu_key_name_length = 0;
4044 Menus &submenus = GetSubmenus();
4045 const size_t num_submenus = submenus.size();
4046 for (size_t i = 0; i < num_submenus; ++i) {
4047 Menu *submenu = submenus[i].get();
4048 if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size())
4049 m_max_submenu_name_length = submenu->m_name.size();
4050 if (static_cast<size_t>(m_max_submenu_key_name_length) <
4051 submenu->m_key_name.size())
4052 m_max_submenu_key_name_length = submenu->m_key_name.size();
4053 }
4054}
4055
4056void Menu::AddSubmenu(const MenuSP &menu_sp) {
4057 menu_sp->m_parent = this;
4058 if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size())
4059 m_max_submenu_name_length = menu_sp->m_name.size();
4060 if (static_cast<size_t>(m_max_submenu_key_name_length) <
4061 menu_sp->m_key_name.size())
4062 m_max_submenu_key_name_length = menu_sp->m_key_name.size();
4063 m_submenus.push_back(menu_sp);
4064}
4065
4066void Menu::DrawMenuTitle(Window &window, bool highlight) {
4067 if (m_type == Type::Separator) {
4068 window.MoveCursor(0, window.GetCursorY());
4069 window.PutChar(ACS_LTEE);
4070 int width = window.GetWidth();
4071 if (width > 2) {
4072 width -= 2;
4073 for (int i = 0; i < width; ++i)
4074 window.PutChar(ACS_HLINE);
4075 }
4076 window.PutChar(ACS_RTEE);
4077 } else {
4078 const int shortcut_key = m_key_value;
4079 bool underlined_shortcut = false;
4080 const attr_t highlight_attr = A_REVERSE;
4081 if (highlight)
4082 window.AttributeOn(highlight_attr);
4083 if (llvm::isPrint(shortcut_key)) {
4084 size_t lower_pos = m_name.find(tolower(shortcut_key));
4085 size_t upper_pos = m_name.find(toupper(shortcut_key));
4086 const char *name = m_name.c_str();
4087 size_t pos = std::min<size_t>(lower_pos, upper_pos);
4088 if (pos != std::string::npos) {
4089 underlined_shortcut = true;
4090 if (pos > 0) {
4091 window.PutCString(name, pos);
4092 name += pos;
4093 }
4094 const attr_t shortcut_attr = A_UNDERLINE | A_BOLD;
4095 window.AttributeOn(shortcut_attr);
4096 window.PutChar(name[0]);
4097 window.AttributeOff(shortcut_attr);
4098 name++;
4099 if (name[0])
4100 window.PutCString(name);
4101 }
4102 }
4103
4104 if (!underlined_shortcut) {
4105 window.PutCString(m_name.c_str());
4106 }
4107
4108 if (highlight)
4109 window.AttributeOff(highlight_attr);
4110
4111 if (m_key_name.empty()) {
4112 if (!underlined_shortcut && llvm::isPrint(m_key_value)) {
4113 window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
4114 window.Printf(" (%c)", m_key_value);
4115 window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
4116 }
4117 } else {
4118 window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
4119 window.Printf(" (%s)", m_key_name.c_str());
4120 window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
4121 }
4122 }
4123}
4124
4125bool Menu::WindowDelegateDraw(Window &window, bool force) {
4126 Menus &submenus = GetSubmenus();
4127 const size_t num_submenus = submenus.size();
4128 const int selected_idx = GetSelectedSubmenuIndex();
4129 Menu::Type menu_type = GetType();
4130 switch (menu_type) {
4131 case Menu::Type::Bar: {
4132 window.SetBackground(BlackOnWhite);
4133 window.MoveCursor(0, 0);
4134 for (size_t i = 0; i < num_submenus; ++i) {
4135 Menu *menu = submenus[i].get();
4136 if (i > 0)
4137 window.PutChar(' ');
4138 menu->SetStartingColumn(window.GetCursorX());
4139 window.PutCString("| ");
4140 menu->DrawMenuTitle(window, false);
4141 }
4142 window.PutCString(" |");
4143 } break;
4144
4145 case Menu::Type::Item: {
4146 int y = 1;
4147 int x = 3;
4148 // Draw the menu
4149 int cursor_x = 0;
4150 int cursor_y = 0;
4151 window.Erase();
4152 window.SetBackground(BlackOnWhite);
4153 window.Box();
4154 for (size_t i = 0; i < num_submenus; ++i) {
4155 const bool is_selected = (i == static_cast<size_t>(selected_idx));
4156 window.MoveCursor(x, y + i);
4157 if (is_selected) {
4158 // Remember where we want the cursor to be
4159 cursor_x = x - 1;
4160 cursor_y = y + i;
4161 }
4162 submenus[i]->DrawMenuTitle(window, is_selected);
4163 }
4164 window.MoveCursor(cursor_x, cursor_y);
4165 } break;
4166
4167 default:
4168 case Menu::Type::Separator:
4169 break;
4170 }
4171 return true; // Drawing handled...
4172}
4173
4174HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) {
4175 HandleCharResult result = eKeyNotHandled;
4176
4177 Menus &submenus = GetSubmenus();
4178 const size_t num_submenus = submenus.size();
4179 const int selected_idx = GetSelectedSubmenuIndex();
4180 Menu::Type menu_type = GetType();
4181 if (menu_type == Menu::Type::Bar) {
4182 MenuSP run_menu_sp;
4183 switch (key) {
4184 case KEY_DOWN:
4185 case KEY_UP:
4186 // Show last menu or first menu
4187 if (selected_idx < static_cast<int>(num_submenus))
4188 run_menu_sp = submenus[selected_idx];
4189 else if (!submenus.empty())
4190 run_menu_sp = submenus.front();
4191 result = eKeyHandled;
4192 break;
4193
4194 case KEY_RIGHT:
4195 ++m_selected;
4196 if (m_selected >= static_cast<int>(num_submenus))
4197 m_selected = 0;
4198 if (m_selected < static_cast<int>(num_submenus))
4199 run_menu_sp = submenus[m_selected];
4200 else if (!submenus.empty())
4201 run_menu_sp = submenus.front();
4202 result = eKeyHandled;
4203 break;
4204
4205 case KEY_LEFT:
4206 --m_selected;
4207 if (m_selected < 0)
4208 m_selected = num_submenus - 1;
4209 if (m_selected < static_cast<int>(num_submenus))
4210 run_menu_sp = submenus[m_selected];
4211 else if (!submenus.empty())
4212 run_menu_sp = submenus.front();
4213 result = eKeyHandled;
4214 break;
4215
4216 default:
4217 for (size_t i = 0; i < num_submenus; ++i) {
4218 if (submenus[i]->GetKeyValue() == key) {
4219 SetSelectedSubmenuIndex(i);
4220 run_menu_sp = submenus[i];
4221 result = eKeyHandled;
4222 break;
4223 }
4224 }
4225 break;
4226 }
4227
4228 if (run_menu_sp) {
4229 // Run the action on this menu in case we need to populate the menu with
4230 // dynamic content and also in case check marks, and any other menu
4231 // decorations need to be calculated
4232 if (run_menu_sp->Action() == MenuActionResult::Quit)
4233 return eQuitApplication;
4234
4235 Rect menu_bounds;
4236 menu_bounds.origin.x = run_menu_sp->GetStartingColumn();
4237 menu_bounds.origin.y = 1;
4238 menu_bounds.size.width = run_menu_sp->GetDrawWidth();
4239 menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2;
4240 if (m_menu_window_sp)
4241 window.GetParent()->RemoveSubWindow(m_menu_window_sp.get());
4242
4243 m_menu_window_sp = window.GetParent()->CreateSubWindow(
4244 run_menu_sp->GetName().c_str(), menu_bounds, true);
4245 m_menu_window_sp->SetDelegate(run_menu_sp);
4246 }
4247 } else if (menu_type == Menu::Type::Item) {
4248 switch (key) {
4249 case KEY_DOWN:
4250 if (m_submenus.size() > 1) {
4251 const int start_select = m_selected;
4252 while (++m_selected != start_select) {
4253 if (static_cast<size_t>(m_selected) >= num_submenus)
4254 m_selected = 0;
4255 if (m_submenus[m_selected]->GetType() == Type::Separator)
4256 continue;
4257 else
4258 break;
4259 }
4260 return eKeyHandled;
4261 }
4262 break;
4263
4264 case KEY_UP:
4265 if (m_submenus.size() > 1) {
4266 const int start_select = m_selected;
4267 while (--m_selected != start_select) {
4268 if (m_selected < static_cast<int>(0))
4269 m_selected = num_submenus - 1;
4270 if (m_submenus[m_selected]->GetType() == Type::Separator)
4271 continue;
4272 else
4273 break;
4274 }
4275 return eKeyHandled;
4276 }
4277 break;
4278
4279 case KEY_RETURN:
4280 if (static_cast<size_t>(selected_idx) < num_submenus) {
4281 if (submenus[selected_idx]->Action() == MenuActionResult::Quit)
4282 return eQuitApplication;
4283 window.GetParent()->RemoveSubWindow(&window);
4284 return eKeyHandled;
4285 }
4286 break;
4287
4288 case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in
4289 // case other chars are entered for escaped sequences
4290 window.GetParent()->RemoveSubWindow(&window);
4291 return eKeyHandled;
4292
4293 default:
4294 for (size_t i = 0; i < num_submenus; ++i) {
4295 Menu *menu = submenus[i].get();
4296 if (menu->GetKeyValue() == key) {
4297 SetSelectedSubmenuIndex(i);
4298 window.GetParent()->RemoveSubWindow(&window);
4299 if (menu->Action() == MenuActionResult::Quit)
4300 return eQuitApplication;
4301 return eKeyHandled;
4302 }
4303 }
4304 break;
4305 }
4306 } else if (menu_type == Menu::Type::Separator) {
4307 }
4308 return result;
4309}
4310
4311class Application {
4312public:
4313 Application(FILE *in, FILE *out) : m_window_sp(), m_in(in), m_out(out) {}
4314
4315 ~Application() {
4316 m_window_delegates.clear();
4317 m_window_sp.reset();
4318 if (m_screen) {
4319 ::delscreen(m_screen);
4320 m_screen = nullptr;
4321 }
4322 }
4323
4324 void Initialize() {
4325 m_screen = ::newterm(nullptr, m_out, m_in);
4326 ::start_color();
4327 ::curs_set(0);
4328 ::noecho();
4329 ::keypad(stdscr, TRUE);
4330 }
4331
4332 void Terminate() { ::endwin(); }
4333
4334 void Run(Debugger &debugger) {
4335 bool done = false;
4336 int delay_in_tenths_of_a_second = 1;
4337
4338 // Alas the threading model in curses is a bit lame so we need to resort
4339 // to polling every 0.5 seconds. We could poll for stdin ourselves and
4340 // then pass the keys down but then we need to translate all of the escape
4341 // sequences ourselves. So we resort to polling for input because we need
4342 // to receive async process events while in this loop.
4343
4344 halfdelay(delay_in_tenths_of_a_second); // Poll using some number of
4345 // tenths of seconds seconds when
4346 // calling Window::GetChar()
4347
4348 ListenerSP listener_sp(
4349 Listener::MakeListener("lldb.IOHandler.curses.Application"));
4350 ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass());
4351 debugger.EnableForwardEvents(listener_sp);
4352
4353 m_update_screen = true;
4354#if defined(__APPLE__)
4355 std::deque<int> escape_chars;
4356#endif
4357
4358 while (!done) {
4359 if (m_update_screen) {
4360 m_window_sp->Draw(false);
4361 // All windows should be calling Window::DeferredRefresh() instead of
4362 // Window::Refresh() so we can do a single update and avoid any screen
4363 // blinking
4364 update_panels();
4365
4366 // Cursor hiding isn't working on MacOSX, so hide it in the top left
4367 // corner
4368 m_window_sp->MoveCursor(0, 0);
4369
4370 doupdate();
4371 m_update_screen = false;
4372 }
4373
4374#if defined(__APPLE__)
4375 // Terminal.app doesn't map its function keys correctly, F1-F4 default
4376 // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if
4377 // possible
4378 int ch;
4379 if (escape_chars.empty())
4380 ch = m_window_sp->GetChar();
4381 else {
4382 ch = escape_chars.front();
4383 escape_chars.pop_front();
4384 }
4385 if (ch == KEY_ESCAPE) {
4386 int ch2 = m_window_sp->GetChar();
4387 if (ch2 == 'O') {
4388 int ch3 = m_window_sp->GetChar();
4389 switch (ch3) {
4390 case 'P':
4391 ch = KEY_F(1);
4392 break;
4393 case 'Q':
4394 ch = KEY_F(2);
4395 break;
4396 case 'R':
4397 ch = KEY_F(3);
4398 break;
4399 case 'S':
4400 ch = KEY_F(4);
4401 break;
4402 default:
4403 escape_chars.push_back(ch2);
4404 if (ch3 != -1)
4405 escape_chars.push_back(ch3);
4406 break;
4407 }
4408 } else if (ch2 != -1)
4409 escape_chars.push_back(ch2);
4410 }
4411#else
4412 int ch = m_window_sp->GetChar();
4413
4414#endif
4415 if (ch == -1) {
4416 if (feof(m_in) || ferror(m_in)) {
4417 done = true;
4418 } else {
4419 // Just a timeout from using halfdelay(), check for events
4420 EventSP event_sp;
4421 while (listener_sp->PeekAtNextEvent()) {
4422 listener_sp->GetEvent(event_sp, std::chrono::seconds(0));
4423
4424 if (event_sp) {
4425 Broadcaster *broadcaster = event_sp->GetBroadcaster();
4426 if (broadcaster) {
4427 // uint32_t event_type = event_sp->GetType();
4428 ConstString broadcaster_class(
4429 broadcaster->GetBroadcasterClass());
4430 if (broadcaster_class == broadcaster_class_process) {
4431 m_update_screen = true;
4432 continue; // Don't get any key, just update our view
4433 }
4434 }
4435 }
4436 }
4437 }
4438 } else {
4439 HandleCharResult key_result = m_window_sp->HandleChar(ch);
4440 switch (key_result) {
4441 case eKeyHandled:
4442 m_update_screen = true;
4443 break;
4444 case eKeyNotHandled:
4445 if (ch == 12) { // Ctrl+L, force full redraw
4446 redrawwin(m_window_sp->get());
4447 m_update_screen = true;
4448 }
4449 break;
4450 case eQuitApplication:
4451 done = true;
4452 break;
4453 }
4454 }
4455 }
4456
4457 debugger.CancelForwardEvents(listener_sp);
4458 }
4459
4460 WindowSP &GetMainWindow() {
4461 if (!m_window_sp)
4462 m_window_sp = std::make_shared<Window>("main", stdscr, false);
4463 return m_window_sp;
4464 }
4465
4466 void TerminalSizeChanged() {
4467 ::endwin();
4468 ::refresh();
4469 Rect content_bounds = m_window_sp->GetFrame();
4470 m_window_sp->SetBounds(content_bounds);
4471 if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar"))
4472 menubar_window_sp->SetBounds(content_bounds.MakeMenuBar());
4473 if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status"))
4474 status_window_sp->SetBounds(content_bounds.MakeStatusBar());
4475
4476 WindowSP source_window_sp = m_window_sp->FindSubWindow("Source");
4477 WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables");
4478 WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers");
4479 WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads");
4480
4481 Rect threads_bounds;
4482 Rect source_variables_bounds;
4483 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
4484 threads_bounds);
4485 if (threads_window_sp)
4486 threads_window_sp->SetBounds(threads_bounds);
4487 else
4488 source_variables_bounds = content_bounds;
4489
4490 Rect source_bounds;
4491 Rect variables_registers_bounds;
4492 source_variables_bounds.HorizontalSplitPercentage(
4493 0.70, source_bounds, variables_registers_bounds);
4494 if (variables_window_sp || registers_window_sp) {
4495 if (variables_window_sp && registers_window_sp) {
4496 Rect variables_bounds;
4497 Rect registers_bounds;
4498 variables_registers_bounds.VerticalSplitPercentage(
4499 0.50, variables_bounds, registers_bounds);
4500 variables_window_sp->SetBounds(variables_bounds);
4501 registers_window_sp->SetBounds(registers_bounds);
4502 } else if (variables_window_sp) {
4503 variables_window_sp->SetBounds(variables_registers_bounds);
4504 } else {
4505 registers_window_sp->SetBounds(variables_registers_bounds);
4506 }
4507 } else {
4508 source_bounds = source_variables_bounds;
4509 }
4510
4511 source_window_sp->SetBounds(source_bounds);
4512
4513 touchwin(stdscr);
4514 redrawwin(m_window_sp->get());
4515 m_update_screen = true;
4516 }
4517
4518protected:
4519 WindowSP m_window_sp;
4520 WindowDelegates m_window_delegates;
4521 SCREEN *m_screen = nullptr;
4522 FILE *m_in;
4523 FILE *m_out;
4524 bool m_update_screen = false;
4525};
4526
4527} // namespace curses
4528} // namespace lldb_private
4529
4530using namespace lldb_private::curses;
4531
4532struct Row {
4533 ValueObjectUpdater value;
4534 Row *parent;
4535 // The process stop ID when the children were calculated.
4536 uint32_t children_stop_id = 0;
4537 int row_idx = 0;
4538 int x = 1;
4539 int y = 1;
4540 bool might_have_children;
4541 bool expanded = false;
4542 bool calculated_children = false;
4543 std::vector<Row> children;
4544
4545 Row(const ValueObjectSP &v, Row *p)
4546 : value(v), parent(p),
4547 might_have_children(v ? v->MightHaveChildren() : false) {}
4548
4549 size_t GetDepth() const {
4550 if (parent)
4551 return 1 + parent->GetDepth();
4552 return 0;
4553 }
4554
4555 void Expand() { expanded = true; }
4556
4557 std::vector<Row> &GetChildren() {
4558 ProcessSP process_sp = value.GetProcessSP();
4559 auto stop_id = process_sp->GetStopID();
4560 if (process_sp && stop_id != children_stop_id) {
4561 children_stop_id = stop_id;
4562 calculated_children = false;
4563 }
4564 if (!calculated_children) {
4565 children.clear();
4566 calculated_children = true;
4567 ValueObjectSP valobj = value.GetSP();
4568 if (valobj) {
4569 const uint32_t num_children = valobj->GetNumChildrenIgnoringErrors();
4570 for (size_t i = 0; i < num_children; ++i) {
4571 children.push_back(Row(valobj->GetChildAtIndex(i), this));
4572 }
4573 }
4574 }
4575 return children;
4576 }
4577
4578 void Unexpand() {
4579 expanded = false;
4580 calculated_children = false;
4581 children.clear();
4582 }
4583
4584 void DrawTree(Window &window) {
4585 if (parent)
4586 parent->DrawTreeForChild(window, this, 0);
4587
4588 if (might_have_children &&
4589 (!calculated_children || !GetChildren().empty())) {
4590 // It we can get UTF8 characters to work we should try to use the
4591 // "symbol" UTF8 string below
4592 // const char *symbol = "";
4593 // if (row.expanded)
4594 // symbol = "\xe2\x96\xbd ";
4595 // else
4596 // symbol = "\xe2\x96\xb7 ";
4597 // window.PutCString (symbol);
4598
4599 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v'
4600 // or '>' character...
4601 // if (expanded)
4602 // window.PutChar (ACS_DARROW);
4603 // else
4604 // window.PutChar (ACS_RARROW);
4605 // Since we can't find any good looking right arrow/down arrow symbols,
4606 // just use a diamond...
4607 window.PutChar(ACS_DIAMOND);
4608 window.PutChar(ACS_HLINE);
4609 }
4610 }
4611
4612 void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) {
4613 if (parent)
4614 parent->DrawTreeForChild(window, this, reverse_depth + 1);
4615
4616 if (&GetChildren().back() == child) {
4617 // Last child
4618 if (reverse_depth == 0) {
4619 window.PutChar(ACS_LLCORNER);
4620 window.PutChar(ACS_HLINE);
4621 } else {
4622 window.PutChar(' ');
4623 window.PutChar(' ');
4624 }
4625 } else {
4626 if (reverse_depth == 0) {
4627 window.PutChar(ACS_LTEE);
4628 window.PutChar(ACS_HLINE);
4629 } else {
4630 window.PutChar(ACS_VLINE);
4631 window.PutChar(' ');
4632 }
4633 }
4634 }
4635};
4636
4637struct DisplayOptions {
4638 bool show_types;
4639};
4640
4641class TreeItem;
4642
4643class TreeDelegate {
4644public:
4645 TreeDelegate() = default;
4646 virtual ~TreeDelegate() = default;
4647
4648 virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0;
4649 virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0;
4650 virtual void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
4651 TreeItem *&selected_item) {}
4652 // This is invoked when a tree item is selected. If true is returned, the
4653 // views are updated.
4654 virtual bool TreeDelegateItemSelected(TreeItem &item) = 0;
4655 virtual bool TreeDelegateExpandRootByDefault() { return false; }
4656 // This is mostly useful for root tree delegates. If false is returned,
4657 // drawing will be skipped completely. This is needed, for instance, in
4658 // skipping drawing of the threads tree if there is no running process.
4659 virtual bool TreeDelegateShouldDraw() { return true; }
4660};
4661
4662typedef std::shared_ptr<TreeDelegate> TreeDelegateSP;
4663
4664struct TreeItemData {
4665 TreeItemData(TreeItem *parent, TreeDelegate &delegate,
4666 bool might_have_children, bool is_expanded)
4667 : m_parent(parent), m_delegate(&delegate),
4668 m_might_have_children(might_have_children), m_is_expanded(is_expanded) {
4669 }
4670
4671protected:
4672 TreeItem *m_parent;
4673 TreeDelegate *m_delegate;
4674 void *m_user_data = nullptr;
4675 uint64_t m_identifier = 0;
4676 std::string m_text;
4677 int m_row_idx = -1; // Zero based visible row index, -1 if not visible or for
4678 // the root item
4679 bool m_might_have_children;
4680 bool m_is_expanded = false;
4681};
4682
4683class TreeItem : public TreeItemData {
4684public:
4685 TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children)
4686 : TreeItemData(parent, delegate, might_have_children,
4687 parent == nullptr
4688 ? delegate.TreeDelegateExpandRootByDefault()
4689 : false),
4690 m_children() {}
4691
4692 TreeItem(const TreeItem &) = delete;
4693 TreeItem &operator=(const TreeItem &rhs) = delete;
4694
4695 TreeItem &operator=(TreeItem &&rhs) {
4696 if (this != &rhs) {
4697 TreeItemData::operator=(std::move(rhs));
4698 AdoptChildren(rhs.m_children);
4699 }
4700 return *this;
4701 }
4702
4703 TreeItem(TreeItem &&rhs) : TreeItemData(std::move(rhs)) {
4704 AdoptChildren(rhs.m_children);
4705 }
4706
4707 size_t GetDepth() const {
4708 if (m_parent)
4709 return 1 + m_parent->GetDepth();
4710 return 0;
4711 }
4712
4713 int GetRowIndex() const { return m_row_idx; }
4714
4715 void ClearChildren() { m_children.clear(); }
4716
4717 void Resize(size_t n, TreeDelegate &delegate, bool might_have_children) {
4718 if (m_children.size() >= n) {
4719 m_children.erase(m_children.begin() + n, m_children.end());
4720 return;
4721 }
4722 m_children.reserve(n);
4723 std::generate_n(std::back_inserter(m_children), n - m_children.size(),
4724 [&, parent = this]() {
4725 return TreeItem(parent, delegate, might_have_children);
4726 });
4727 }
4728
4729 TreeItem &operator[](size_t i) { return m_children[i]; }
4730
4731 void SetRowIndex(int row_idx) { m_row_idx = row_idx; }
4732
4733 size_t GetNumChildren() {
4734 m_delegate->TreeDelegateGenerateChildren(*this);
4735 return m_children.size();
4736 }
4737
4738 void ItemWasSelected() { m_delegate->TreeDelegateItemSelected(*this); }
4739
4740 void CalculateRowIndexes(int &row_idx) {
4741 SetRowIndex(row_idx);
4742 ++row_idx;
4743
4744 const bool expanded = IsExpanded();
4745
4746 // The root item must calculate its children, or we must calculate the
4747 // number of children if the item is expanded
4748 if (m_parent == nullptr || expanded)
4749 GetNumChildren();
4750
4751 for (auto &item : m_children) {
4752 if (expanded)
4753 item.CalculateRowIndexes(row_idx);
4754 else
4755 item.SetRowIndex(-1);
4756 }
4757 }
4758
4759 TreeItem *GetParent() { return m_parent; }
4760
4761 bool IsExpanded() const { return m_is_expanded; }
4762
4763 void Expand() { m_is_expanded = true; }
4764
4765 void Unexpand() { m_is_expanded = false; }
4766
4767 bool Draw(Window &window, const int first_visible_row,
4768 const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) {
4769 if (num_rows_left <= 0)
4770 return false;
4771
4772 if (m_row_idx >= first_visible_row) {
4773 window.MoveCursor(2, row_idx + 1);
4774
4775 if (m_parent)
4776 m_parent->DrawTreeForChild(window, this, 0);
4777
4778 if (m_might_have_children) {
4779 // It we can get UTF8 characters to work we should try to use the
4780 // "symbol" UTF8 string below
4781 // const char *symbol = "";
4782 // if (row.expanded)
4783 // symbol = "\xe2\x96\xbd ";
4784 // else
4785 // symbol = "\xe2\x96\xb7 ";
4786 // window.PutCString (symbol);
4787
4788 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a
4789 // 'v' or '>' character...
4790 // if (expanded)
4791 // window.PutChar (ACS_DARROW);
4792 // else
4793 // window.PutChar (ACS_RARROW);
4794 // Since we can't find any good looking right arrow/down arrow symbols,
4795 // just use a diamond...
4796 window.PutChar(ACS_DIAMOND);
4797 window.PutChar(ACS_HLINE);
4798 }
4799 bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) &&
4800 window.IsActive();
4801
4802 if (highlight)
4803 window.AttributeOn(A_REVERSE);
4804
4805 m_delegate->TreeDelegateDrawTreeItem(*this, window);
4806
4807 if (highlight)
4808 window.AttributeOff(A_REVERSE);
4809 ++row_idx;
4810 --num_rows_left;
4811 }
4812
4813 if (num_rows_left <= 0)
4814 return false; // We are done drawing...
4815
4816 if (IsExpanded()) {
4817 for (auto &item : m_children) {
4818 // If we displayed all the rows and item.Draw() returns false we are
4819 // done drawing and can exit this for loop
4820 if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx,
4821 num_rows_left))
4822 break;
4823 }
4824 }
4825 return num_rows_left >= 0; // Return true if not done drawing yet
4826 }
4827
4828 void DrawTreeForChild(Window &window, TreeItem *child,
4829 uint32_t reverse_depth) {
4830 if (m_parent)
4831 m_parent->DrawTreeForChild(window, this, reverse_depth + 1);
4832
4833 if (&m_children.back() == child) {
4834 // Last child
4835 if (reverse_depth == 0) {
4836 window.PutChar(ACS_LLCORNER);
4837 window.PutChar(ACS_HLINE);
4838 } else {
4839 window.PutChar(' ');
4840 window.PutChar(' ');
4841 }
4842 } else {
4843 if (reverse_depth == 0) {
4844 window.PutChar(ACS_LTEE);
4845 window.PutChar(ACS_HLINE);
4846 } else {
4847 window.PutChar(ACS_VLINE);
4848 window.PutChar(' ');
4849 }
4850 }
4851 }
4852
4853 TreeItem *GetItemForRowIndex(uint32_t row_idx) {
4854 if (static_cast<uint32_t>(m_row_idx) == row_idx)
4855 return this;
4856 if (m_children.empty())
4857 return nullptr;
4858 if (IsExpanded()) {
4859 for (auto &item : m_children) {
4860 TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx);
4861 if (selected_item_ptr)
4862 return selected_item_ptr;
4863 }
4864 }
4865 return nullptr;
4866 }
4867
4868 void *GetUserData() const { return m_user_data; }
4869
4870 void SetUserData(void *user_data) { m_user_data = user_data; }
4871
4872 uint64_t GetIdentifier() const { return m_identifier; }
4873
4874 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
4875
4876 const std::string &GetText() const { return m_text; }
4877
4878 void SetText(const char *text) {
4879 if (text == nullptr) {
4880 m_text.clear();
4881 return;
4882 }
4883 m_text = text;
4884 }
4885
4886 void SetMightHaveChildren(bool b) { m_might_have_children = b; }
4887
4888protected:
4889 void AdoptChildren(std::vector<TreeItem> &children) {
4890 m_children = std::move(children);
4891 for (auto &child : m_children)
4892 child.m_parent = this;
4893 }
4894
4895 std::vector<TreeItem> m_children;
4896};
4897
4898class TreeWindowDelegate : public WindowDelegate {
4899public:
4900 TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp)
4901 : m_debugger(debugger), m_delegate_sp(delegate_sp),
4902 m_root(nullptr, *delegate_sp, true) {}
4903
4904 int NumVisibleRows() const { return m_max_y - m_min_y; }
4905
4906 bool WindowDelegateDraw(Window &window, bool force) override {
4907 m_min_x = 2;
4908 m_min_y = 1;
4909 m_max_x = window.GetWidth() - 1;
4910 m_max_y = window.GetHeight() - 1;
4911
4912 window.Erase();
4913 window.DrawTitleBox(window.GetName());
4914
4915 if (!m_delegate_sp->TreeDelegateShouldDraw()) {
4916 m_selected_item = nullptr;
4917 return true;
4918 }
4919
4920 const int num_visible_rows = NumVisibleRows();
4921 m_num_rows = 0;
4922 m_root.CalculateRowIndexes(m_num_rows);
4923 m_delegate_sp->TreeDelegateUpdateSelection(m_root, m_selected_row_idx,
4924 m_selected_item);
4925
4926 // If we unexpanded while having something selected our total number of
4927 // rows is less than the num visible rows, then make sure we show all the
4928 // rows by setting the first visible row accordingly.
4929 if (m_first_visible_row > 0 && m_num_rows < num_visible_rows)
4930 m_first_visible_row = 0;
4931
4932 // Make sure the selected row is always visible
4933 if (m_selected_row_idx < m_first_visible_row)
4934 m_first_visible_row = m_selected_row_idx;
4935 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
4936 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
4937
4938 int row_idx = 0;
4939 int num_rows_left = num_visible_rows;
4940 m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx,
4941 num_rows_left);
4942 // Get the selected row
4943 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4944
4945 return true; // Drawing handled
4946 }
4947
4948 const char *WindowDelegateGetHelpText() override {
4949 return "Thread window keyboard shortcuts:";
4950 }
4951
4952 KeyHelp *WindowDelegateGetKeyHelp() override {
4953 static curses::KeyHelp g_source_view_key_help[] = {
4954 {KEY_UP, "Select previous item"},
4955 {KEY_DOWN, "Select next item"},
4956 {KEY_RIGHT, "Expand the selected item"},
4957 {KEY_LEFT,
4958 "Unexpand the selected item or select parent if not expanded"},
4959 {KEY_PPAGE, "Page up"},
4960 {KEY_NPAGE, "Page down"},
4961 {'h', "Show help dialog"},
4962 {' ', "Toggle item expansion"},
4963 {',', "Page up"},
4964 {'.', "Page down"},
4965 {'\0', nullptr}};
4966 return g_source_view_key_help;
4967 }
4968
4969 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
4970 switch (c) {
4971 case ',':
4972 case KEY_PPAGE:
4973 // Page up key
4974 if (m_first_visible_row > 0) {
4975 if (m_first_visible_row > m_max_y)
4976 m_first_visible_row -= m_max_y;
4977 else
4978 m_first_visible_row = 0;
4979 m_selected_row_idx = m_first_visible_row;
4980 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4981 if (m_selected_item)
4982 m_selected_item->ItemWasSelected();
4983 }
4984 return eKeyHandled;
4985
4986 case '.':
4987 case KEY_NPAGE:
4988 // Page down key
4989 if (m_num_rows > m_max_y) {
4990 if (m_first_visible_row + m_max_y < m_num_rows) {
4991 m_first_visible_row += m_max_y;
4992 m_selected_row_idx = m_first_visible_row;
4993 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4994 if (m_selected_item)
4995 m_selected_item->ItemWasSelected();
4996 }
4997 }
4998 return eKeyHandled;
4999
5000 case KEY_UP:
5001 if (m_selected_row_idx > 0) {
5002 --m_selected_row_idx;
5003 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
5004 if (m_selected_item)
5005 m_selected_item->ItemWasSelected();
5006 }
5007 return eKeyHandled;
5008
5009 case KEY_DOWN:
5010 if (m_selected_row_idx + 1 < m_num_rows) {
5011 ++m_selected_row_idx;
5012 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
5013 if (m_selected_item)
5014 m_selected_item->ItemWasSelected();
5015 }
5016 return eKeyHandled;
5017
5018 case KEY_RIGHT:
5019 if (m_selected_item) {
5020 if (!m_selected_item->IsExpanded())
5021 m_selected_item->Expand();
5022 }
5023 return eKeyHandled;
5024
5025 case KEY_LEFT:
5026 if (m_selected_item) {
5027 if (m_selected_item->IsExpanded())
5028 m_selected_item->Unexpand();
5029 else if (m_selected_item->GetParent()) {
5030 m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex();
5031 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
5032 if (m_selected_item)
5033 m_selected_item->ItemWasSelected();
5034 }
5035 }
5036 return eKeyHandled;
5037
5038 case ' ':
5039 // Toggle expansion state when SPACE is pressed
5040 if (m_selected_item) {
5041 if (m_selected_item->IsExpanded())
5042 m_selected_item->Unexpand();
5043 else
5044 m_selected_item->Expand();
5045 }
5046 return eKeyHandled;
5047
5048 case 'h':
5049 window.CreateHelpSubwindow();
5050 return eKeyHandled;
5051
5052 default:
5053 break;
5054 }
5055 return eKeyNotHandled;
5056 }
5057
5058protected:
5059 Debugger &m_debugger;
5060 TreeDelegateSP m_delegate_sp;
5061 TreeItem m_root;
5062 TreeItem *m_selected_item = nullptr;
5063 int m_num_rows = 0;
5064 int m_selected_row_idx = 0;
5065 int m_first_visible_row = 0;
5066 int m_min_x = 0;
5067 int m_min_y = 0;
5068 int m_max_x = 0;
5069 int m_max_y = 0;
5070};
5071
5072// A tree delegate that just draws the text member of the tree item, it doesn't
5073// have any children or actions.
5074class TextTreeDelegate : public TreeDelegate {
5075public:
5076 TextTreeDelegate() : TreeDelegate() {}
5077
5078 ~TextTreeDelegate() override = default;
5079
5080 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5081 window.PutCStringTruncated(1, item.GetText().c_str());
5082 }
5083
5084 void TreeDelegateGenerateChildren(TreeItem &item) override {}
5085
5086 bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5087};
5088
5089class FrameTreeDelegate : public TreeDelegate {
5090public:
5091 FrameTreeDelegate() : TreeDelegate() {
5093 "#${frame.index}: {${function.name}${function.pc-offset}}}", m_format);
5094 }
5095
5096 ~FrameTreeDelegate() override = default;
5097
5098 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5099 Thread *thread = (Thread *)item.GetUserData();
5100 if (thread) {
5101 const uint64_t frame_idx = item.GetIdentifier();
5102 StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx);
5103 if (frame_sp) {
5104 StreamString strm;
5105 const SymbolContext &sc =
5106 frame_sp->GetSymbolContext(eSymbolContextEverything);
5107 ExecutionContext exe_ctx(frame_sp);
5108 if (FormatEntity::Formatter(&sc, &exe_ctx, nullptr, false, false)
5109 .Format(m_format, strm)) {
5110 int right_pad = 1;
5111 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
5112 }
5113 }
5114 }
5115 }
5116
5117 void TreeDelegateGenerateChildren(TreeItem &item) override {
5118 // No children for frames yet...
5119 }
5120
5121 bool TreeDelegateItemSelected(TreeItem &item) override {
5122 Thread *thread = (Thread *)item.GetUserData();
5123 if (thread) {
5124 thread->GetProcess()->GetThreadList().SetSelectedThreadByID(
5125 thread->GetID());
5126 const uint64_t frame_idx = item.GetIdentifier();
5127 thread->SetSelectedFrameByIndex(frame_idx);
5128 return true;
5129 }
5130 return false;
5131 }
5132
5133protected:
5134 FormatEntity::Entry m_format;
5135};
5136
5137class ThreadTreeDelegate : public TreeDelegate {
5138public:
5139 ThreadTreeDelegate(Debugger &debugger)
5140 : TreeDelegate(), m_debugger(debugger) {
5141 FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop "
5142 "reason = ${thread.stop-reason}}",
5143 m_format);
5144 }
5145
5146 ~ThreadTreeDelegate() override = default;
5147
5148 ProcessSP GetProcess() {
5149 return m_debugger.GetCommandInterpreter()
5150 .GetExecutionContext()
5151 .GetProcessSP();
5152 }
5153
5154 ThreadSP GetThread(const TreeItem &item) {
5155 ProcessSP process_sp = GetProcess();
5156 if (process_sp)
5157 return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier());
5158 return ThreadSP();
5159 }
5160
5161 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5162 ThreadSP thread_sp = GetThread(item);
5163 if (thread_sp) {
5164 StreamString strm;
5165 ExecutionContext exe_ctx(thread_sp);
5166 if (FormatEntity::Formatter(nullptr, &exe_ctx, nullptr, false, false)
5167 .Format(m_format, strm)) {
5168 int right_pad = 1;
5169 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
5170 }
5171 }
5172 }
5173
5174 void TreeDelegateGenerateChildren(TreeItem &item) override {
5175 ProcessSP process_sp = GetProcess();
5176 if (process_sp && process_sp->IsAlive()) {
5177 StateType state = process_sp->GetState();
5178 if (StateIsStoppedState(state, true)) {
5179 ThreadSP thread_sp = GetThread(item);
5180 if (thread_sp) {
5181 if (m_stop_id == process_sp->GetStopID() &&
5182 thread_sp->GetID() == m_tid)
5183 return; // Children are already up to date
5184 if (!m_frame_delegate_sp) {
5185 // Always expand the thread item the first time we show it
5186 m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>();
5187 }
5188
5189 m_stop_id = process_sp->GetStopID();
5190 m_tid = thread_sp->GetID();
5191
5192 size_t num_frames = thread_sp->GetStackFrameCount();
5193 item.Resize(num_frames, *m_frame_delegate_sp, false);
5194 for (size_t i = 0; i < num_frames; ++i) {
5195 item[i].SetUserData(thread_sp.get());
5196 item[i].SetIdentifier(i);
5197 }
5198 }
5199 return;
5200 }
5201 }
5202 item.ClearChildren();
5203 }
5204
5205 bool TreeDelegateItemSelected(TreeItem &item) override {
5206 ProcessSP process_sp = GetProcess();
5207 if (process_sp && process_sp->IsAlive()) {
5208 StateType state = process_sp->GetState();
5209 if (StateIsStoppedState(state, true)) {
5210 ThreadSP thread_sp = GetThread(item);
5211 if (thread_sp) {
5212 ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList();
5213 std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex());
5214 ThreadSP selected_thread_sp = thread_list.GetSelectedThread();
5215 if (selected_thread_sp->GetID() != thread_sp->GetID()) {
5216 thread_list.SetSelectedThreadByID(thread_sp->GetID());
5217 return true;
5218 }
5219 }
5220 }
5221 }
5222 return false;
5223 }
5224
5225protected:
5226 Debugger &m_debugger;
5227 std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp;
5229 uint32_t m_stop_id = UINT32_MAX;
5230 FormatEntity::Entry m_format;
5231};
5232
5233class ThreadsTreeDelegate : public TreeDelegate {
5234public:
5235 ThreadsTreeDelegate(Debugger &debugger)
5236 : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger) {
5237 FormatEntity::Parse("process ${process.id}{, name = ${process.name}}",
5238 m_format);
5239 }
5240
5241 ~ThreadsTreeDelegate() override = default;
5242
5243 ProcessSP GetProcess() {
5244 return m_debugger.GetCommandInterpreter()
5245 .GetExecutionContext()
5246 .GetProcessSP();
5247 }
5248
5249 bool TreeDelegateShouldDraw() override {
5250 ProcessSP process = GetProcess();
5251 if (!process)
5252 return false;
5253
5254 if (StateIsRunningState(process->GetState()))
5255 return false;
5256
5257 return true;
5258 }
5259
5260 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5261 ProcessSP process_sp = GetProcess();
5262 if (process_sp && process_sp->IsAlive()) {
5263 StreamString strm;
5264 ExecutionContext exe_ctx(process_sp);
5265 if (FormatEntity::Formatter(nullptr, &exe_ctx, nullptr, false, false)
5266 .Format(m_format, strm)) {
5267 int right_pad = 1;
5268 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
5269 }
5270 }
5271 }
5272
5273 void TreeDelegateGenerateChildren(TreeItem &item) override {
5274 ProcessSP process_sp = GetProcess();
5275 m_update_selection = false;
5276 if (process_sp && process_sp->IsAlive()) {
5277 StateType state = process_sp->GetState();
5278 if (StateIsStoppedState(state, true)) {
5279 const uint32_t stop_id = process_sp->GetStopID();
5280 if (m_stop_id == stop_id)
5281 return; // Children are already up to date
5282
5283 m_stop_id = stop_id;
5284 m_update_selection = true;
5285
5286 if (!m_thread_delegate_sp) {
5287 // Always expand the thread item the first time we show it
5288 // item.Expand();
5289 m_thread_delegate_sp =
5290 std::make_shared<ThreadTreeDelegate>(m_debugger);
5291 }
5292
5293 ThreadList &threads = process_sp->GetThreadList();
5294 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
5295 ThreadSP selected_thread = threads.GetSelectedThread();
5296 size_t num_threads = threads.GetSize();
5297 item.Resize(num_threads, *m_thread_delegate_sp, false);
5298 for (size_t i = 0; i < num_threads; ++i) {
5299 ThreadSP thread = threads.GetThreadAtIndex(i);
5300 item[i].SetIdentifier(thread->GetID());
5301 item[i].SetMightHaveChildren(true);
5302 if (selected_thread->GetID() == thread->GetID())
5303 item[i].Expand();
5304 }
5305 return;
5306 }
5307 }
5308 item.ClearChildren();
5309 }
5310
5311 void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
5312 TreeItem *&selected_item) override {
5313 if (!m_update_selection)
5314 return;
5315
5316 ProcessSP process_sp = GetProcess();
5317 if (!(process_sp && process_sp->IsAlive()))
5318 return;
5319
5320 StateType state = process_sp->GetState();
5321 if (!StateIsStoppedState(state, true))
5322 return;
5323
5324 ThreadList &threads = process_sp->GetThreadList();
5325 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
5326 ThreadSP selected_thread = threads.GetSelectedThread();
5327 size_t num_threads = threads.GetSize();
5328 for (size_t i = 0; i < num_threads; ++i) {
5329 ThreadSP thread = threads.GetThreadAtIndex(i);
5330 if (selected_thread->GetID() == thread->GetID()) {
5331 selected_item =
5332 &root[i][thread->GetSelectedFrameIndex(SelectMostRelevantFrame)];
5333 selection_index = selected_item->GetRowIndex();
5334 return;
5335 }
5336 }
5337 }
5338
5339 bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5340
5341 bool TreeDelegateExpandRootByDefault() override { return true; }
5342
5343protected:
5344 std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp;
5345 Debugger &m_debugger;
5346 uint32_t m_stop_id = UINT32_MAX;
5347 bool m_update_selection = false;
5348 FormatEntity::Entry m_format;
5349};
5350
5351class BreakpointLocationTreeDelegate : public TreeDelegate {
5352public:
5353 BreakpointLocationTreeDelegate(Debugger &debugger)
5354 : TreeDelegate(), m_debugger(debugger) {}
5355
5356 ~BreakpointLocationTreeDelegate() override = default;
5357
5358 Process *GetProcess() {
5359 ExecutionContext exe_ctx(
5360 m_debugger.GetCommandInterpreter().GetExecutionContext());
5361 return exe_ctx.GetProcessPtr();
5362 }
5363
5364 BreakpointLocationSP GetBreakpointLocation(const TreeItem &item) {
5365 Breakpoint *breakpoint = (Breakpoint *)item.GetUserData();
5366 return breakpoint->GetLocationAtIndex(item.GetIdentifier());
5367 }
5368
5369 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5370 BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item);
5371 Process *process = GetProcess();
5372 StreamString stream;
5373 stream.Printf("%i.%i: ", breakpoint_location->GetBreakpoint().GetID(),
5374 breakpoint_location->GetID());
5375 Address address = breakpoint_location->GetAddress();
5376 address.Dump(&stream, process, Address::DumpStyleResolvedDescription,
5378 window.PutCStringTruncated(1, stream.GetString().str().c_str());
5379 }
5380
5381 StringList ComputeDetailsList(BreakpointLocationSP breakpoint_location) {
5382 StringList details;
5383
5384 Address address = breakpoint_location->GetAddress();
5385 SymbolContext symbol_context;
5386 address.CalculateSymbolContext(&symbol_context);
5387
5388 if (symbol_context.module_sp) {
5389 StreamString module_stream;
5390 module_stream.PutCString("module = ");
5391 symbol_context.module_sp->GetFileSpec().Dump(
5392 module_stream.AsRawOstream());
5393 details.AppendString(module_stream.GetString());
5394 }
5395
5396 if (symbol_context.comp_unit != nullptr) {
5397 StreamString compile_unit_stream;
5398 compile_unit_stream.PutCString("compile unit = ");
5399 symbol_context.comp_unit->GetPrimaryFile().GetFilename().Dump(
5400 &compile_unit_stream);
5401 details.AppendString(compile_unit_stream.GetString());
5402
5403 if (symbol_context.function != nullptr) {
5404 StreamString function_stream;
5405 function_stream.PutCString("function = ");
5406 function_stream.PutCString(
5407 symbol_context.function->GetName().AsCString("<unknown>"));
5408 details.AppendString(function_stream.GetString());
5409 }
5410
5411 if (symbol_context.line_entry.line > 0) {
5412 StreamString location_stream;
5413 location_stream.PutCString("location = ");
5414 symbol_context.line_entry.DumpStopContext(&location_stream, true);
5415 details.AppendString(location_stream.GetString());
5416 }
5417
5418 } else {
5419 if (symbol_context.symbol) {
5420 StreamString symbol_stream;
5421 if (breakpoint_location->IsReExported())
5422 symbol_stream.PutCString("re-exported target = ");
5423 else
5424 symbol_stream.PutCString("symbol = ");
5425 symbol_stream.PutCString(
5426 symbol_context.symbol->GetName().AsCString("<unknown>"));
5427 details.AppendString(symbol_stream.GetString());
5428 }
5429 }
5430
5431 Process *process = GetProcess();
5432
5433 StreamString address_stream;
5434 address.Dump(&address_stream, process, Address::DumpStyleLoadAddress,
5436 details.AppendString(address_stream.GetString());
5437
5438 BreakpointSiteSP breakpoint_site = breakpoint_location->GetBreakpointSite();
5439 if (breakpoint_location->IsIndirect() && breakpoint_site) {
5440 Address resolved_address;
5441 resolved_address.SetLoadAddress(breakpoint_site->GetLoadAddress(),
5442 &breakpoint_location->GetTarget());
5443 const Symbol *resolved_symbol =
5444 resolved_address.CalculateSymbolContextSymbol();
5445 if (resolved_symbol) {
5446 StreamString indirect_target_stream;
5447 indirect_target_stream.PutCString("indirect target = ");
5448 indirect_target_stream.PutCString(
5449 resolved_symbol->GetName().GetCString());
5450 details.AppendString(indirect_target_stream.GetString());
5451 }
5452 }
5453
5454 bool is_resolved = breakpoint_location->IsResolved();
5455 StreamString resolved_stream;
5456 resolved_stream.Printf("resolved = %s", is_resolved ? "true" : "false");
5457 details.AppendString(resolved_stream.GetString());
5458
5459 bool is_hardware = is_resolved && breakpoint_site->IsHardware();
5460 StreamString hardware_stream;
5461 hardware_stream.Printf("hardware = %s", is_hardware ? "true" : "false");
5462 details.AppendString(hardware_stream.GetString());
5463
5464 StreamString hit_count_stream;
5465 hit_count_stream.Printf("hit count = %-4u",
5466 breakpoint_location->GetHitCount());
5467 details.AppendString(hit_count_stream.GetString());
5468
5469 return details;
5470 }
5471
5472 void TreeDelegateGenerateChildren(TreeItem &item) override {
5473 BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item);
5474 StringList details = ComputeDetailsList(breakpoint_location);
5475
5476 if (!m_string_delegate_sp)
5477 m_string_delegate_sp = std::make_shared<TextTreeDelegate>();
5478
5479 item.Resize(details.GetSize(), *m_string_delegate_sp, false);
5480 for (size_t i = 0; i < details.GetSize(); i++) {
5481 item[i].SetText(details.GetStringAtIndex(i));
5482 }
5483 }
5484
5485 bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5486
5487protected:
5488 Debugger &m_debugger;
5489 std::shared_ptr<TextTreeDelegate> m_string_delegate_sp;
5490};
5491
5492class BreakpointTreeDelegate : public TreeDelegate {
5493public:
5494 BreakpointTreeDelegate(Debugger &debugger)
5495 : TreeDelegate(), m_debugger(debugger),
5496 m_breakpoint_location_delegate_sp() {}
5497
5498 ~BreakpointTreeDelegate() override = default;
5499
5500 BreakpointSP GetBreakpoint(const TreeItem &item) {
5501 TargetSP target = m_debugger
5502 .GetSelectedExecutionContext(
5503 /*adopt_dummy_target=*/false)
5504 .GetTargetSP();
5505 BreakpointList &breakpoints = target->GetBreakpointList(false);
5506 return breakpoints.GetBreakpointAtIndex(item.GetIdentifier());
5507 }
5508
5509 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5510 BreakpointSP breakpoint = GetBreakpoint(item);
5511 StreamString stream;
5512 stream.Format("{0}: ", breakpoint->GetID());
5513 breakpoint->GetResolverDescription(&stream);
5514 breakpoint->GetFilterDescription(&stream);
5515 window.PutCStringTruncated(1, stream.GetString().str().c_str());
5516 }
5517
5518 void TreeDelegateGenerateChildren(TreeItem &item) override {
5519 BreakpointSP breakpoint = GetBreakpoint(item);
5520
5521 if (!m_breakpoint_location_delegate_sp)
5522 m_breakpoint_location_delegate_sp =
5523 std::make_shared<BreakpointLocationTreeDelegate>(m_debugger);
5524
5525 item.Resize(breakpoint->GetNumLocations(),
5526 *m_breakpoint_location_delegate_sp, true);
5527 for (size_t i = 0; i < breakpoint->GetNumLocations(); i++) {
5528 item[i].SetIdentifier(i);
5529 item[i].SetUserData(breakpoint.get());
5530 }
5531 }
5532
5533 bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5534
5535protected:
5536 Debugger &m_debugger;
5537 std::shared_ptr<BreakpointLocationTreeDelegate>
5538 m_breakpoint_location_delegate_sp;
5539};
5540
5541class BreakpointsTreeDelegate : public TreeDelegate {
5542public:
5543 BreakpointsTreeDelegate(Debugger &debugger)
5544 : TreeDelegate(), m_debugger(debugger), m_breakpoint_delegate_sp() {}
5545
5546 ~BreakpointsTreeDelegate() override = default;
5547
5548 bool TreeDelegateShouldDraw() override {
5549 TargetSP target = m_debugger
5550 .GetSelectedExecutionContext(
5551 /*adopt_dummy_target=*/false)
5552 .GetTargetSP();
5553 if (!target)
5554 return false;
5555
5556 return true;
5557 }
5558
5559 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5560 window.PutCString("Breakpoints");
5561 }
5562
5563 void TreeDelegateGenerateChildren(TreeItem &item) override {
5564 TargetSP target = m_debugger
5565 .GetSelectedExecutionContext(
5566 /*adopt_dummy_target=*/false)
5567 .GetTargetSP();
5568
5569 BreakpointList &breakpoints = target->GetBreakpointList(false);
5570 std::unique_lock<std::recursive_mutex> lock;
5571 breakpoints.GetListMutex(lock);
5572
5573 if (!m_breakpoint_delegate_sp)
5574 m_breakpoint_delegate_sp =
5575 std::make_shared<BreakpointTreeDelegate>(m_debugger);
5576
5577 item.Resize(breakpoints.GetSize(), *m_breakpoint_delegate_sp, true);
5578 for (size_t i = 0; i < breakpoints.GetSize(); i++) {
5579 item[i].SetIdentifier(i);
5580 }
5581 }
5582
5583 bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5584
5585 bool TreeDelegateExpandRootByDefault() override { return true; }
5586
5587protected:
5588 Debugger &m_debugger;
5589 std::shared_ptr<BreakpointTreeDelegate> m_breakpoint_delegate_sp;
5590};
5591
5592class ValueObjectListDelegate : public WindowDelegate {
5593public:
5594 ValueObjectListDelegate() : m_rows() {}
5595
5596 ValueObjectListDelegate(ValueObjectList &valobj_list) : m_rows() {
5597 SetValues(valobj_list);
5598 }
5599
5600 ~ValueObjectListDelegate() override = default;
5601
5602 void SetValues(ValueObjectList &valobj_list) {
5603 m_selected_row = nullptr;
5604 m_selected_row_idx = 0;
5605 m_first_visible_row = 0;
5606 m_num_rows = 0;
5607 m_rows.clear();
5608 for (auto &valobj_sp : valobj_list.GetObjects())
5609 m_rows.push_back(Row(valobj_sp, nullptr));
5610 }
5611
5612 bool WindowDelegateDraw(Window &window, bool force) override {
5613 m_num_rows = 0;
5614 m_min_x = 2;
5615 m_min_y = 1;
5616 m_max_x = window.GetWidth() - 1;
5617 m_max_y = window.GetHeight() - 1;
5618
5619 window.Erase();
5620 window.DrawTitleBox(window.GetName());
5621
5622 const int num_visible_rows = NumVisibleRows();
5623 const int num_rows = CalculateTotalNumberRows(m_rows);
5624
5625 // If we unexpanded while having something selected our total number of
5626 // rows is less than the num visible rows, then make sure we show all the
5627 // rows by setting the first visible row accordingly.
5628 if (m_first_visible_row > 0 && num_rows < num_visible_rows)
5629 m_first_visible_row = 0;
5630
5631 // Make sure the selected row is always visible
5632 if (m_selected_row_idx < m_first_visible_row)
5633 m_first_visible_row = m_selected_row_idx;
5634 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
5635 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
5636
5637 DisplayRows(window, m_rows, g_options);
5638
5639 // Get the selected row
5640 m_selected_row = GetRowForRowIndex(m_selected_row_idx);
5641 // Keep the cursor on the selected row so the highlight and the cursor are
5642 // always on the same line
5643 if (m_selected_row)
5644 window.MoveCursor(m_selected_row->x, m_selected_row->y);
5645
5646 return true; // Drawing handled
5647 }
5648
5649 KeyHelp *WindowDelegateGetKeyHelp() override {
5650 static curses::KeyHelp g_source_view_key_help[] = {
5651 {KEY_UP, "Select previous item"},
5652 {KEY_DOWN, "Select next item"},
5653 {KEY_RIGHT, "Expand selected item"},
5654 {KEY_LEFT, "Unexpand selected item or select parent if not expanded"},
5655 {KEY_PPAGE, "Page up"},
5656 {KEY_NPAGE, "Page down"},
5657 {'A', "Format as annotated address"},
5658 {'b', "Format as binary"},
5659 {'B', "Format as hex bytes with ASCII"},
5660 {'c', "Format as character"},
5661 {'d', "Format as a signed integer"},
5662 {'D', "Format selected value using the default format for the type"},
5663 {'f', "Format as float"},
5664 {'h', "Show help dialog"},
5665 {'i', "Format as instructions"},
5666 {'o', "Format as octal"},
5667 {'p', "Format as pointer"},
5668 {'s', "Format as C string"},
5669 {'t', "Toggle showing/hiding type names"},
5670 {'u', "Format as an unsigned integer"},
5671 {'x', "Format as hex"},
5672 {'X', "Format as uppercase hex"},
5673 {' ', "Toggle item expansion"},
5674 {',', "Page up"},
5675 {'.', "Page down"},
5676 {'\0', nullptr}};
5677 return g_source_view_key_help;
5678 }
5679
5680 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
5681 switch (c) {
5682 case 'x':
5683 case 'X':
5684 case 'o':
5685 case 's':
5686 case 'u':
5687 case 'd':
5688 case 'D':
5689 case 'i':
5690 case 'A':
5691 case 'p':
5692 case 'c':
5693 case 'b':
5694 case 'B':
5695 case 'f':
5696 // Change the format for the currently selected item
5697 if (m_selected_row) {
5698 auto valobj_sp = m_selected_row->value.GetSP();
5699 if (valobj_sp)
5700 valobj_sp->SetFormat(FormatForChar(c));
5701 }
5702 return eKeyHandled;
5703
5704 case 't':
5705 // Toggle showing type names
5706 g_options.show_types = !g_options.show_types;
5707 return eKeyHandled;
5708
5709 case ',':
5710 case KEY_PPAGE:
5711 // Page up key
5712 if (m_first_visible_row > 0) {
5713 if (static_cast<int>(m_first_visible_row) > m_max_y)
5714 m_first_visible_row -= m_max_y;
5715 else
5716 m_first_visible_row = 0;
5717 m_selected_row_idx = m_first_visible_row;
5718 }
5719 return eKeyHandled;
5720
5721 case '.':
5722 case KEY_NPAGE:
5723 // Page down key
5724 if (m_num_rows > static_cast<size_t>(m_max_y)) {
5725 if (m_first_visible_row + m_max_y < m_num_rows) {
5726 m_first_visible_row += m_max_y;
5727 m_selected_row_idx = m_first_visible_row;
5728 }
5729 }
5730 return eKeyHandled;
5731
5732 case KEY_UP:
5733 if (m_selected_row_idx > 0)
5734 --m_selected_row_idx;
5735 return eKeyHandled;
5736
5737 case KEY_DOWN:
5738 if (m_selected_row_idx + 1 < m_num_rows)
5739 ++m_selected_row_idx;
5740 return eKeyHandled;
5741
5742 case KEY_RIGHT:
5743 if (m_selected_row) {
5744 if (!m_selected_row->expanded)
5745 m_selected_row->Expand();
5746 }
5747 return eKeyHandled;
5748
5749 case KEY_LEFT:
5750 if (m_selected_row) {
5751 if (m_selected_row->expanded)
5752 m_selected_row->Unexpand();
5753 else if (m_selected_row->parent)
5754 m_selected_row_idx = m_selected_row->parent->row_idx;
5755 }
5756 return eKeyHandled;
5757
5758 case ' ':
5759 // Toggle expansion state when SPACE is pressed
5760 if (m_selected_row) {
5761 if (m_selected_row->expanded)
5762 m_selected_row->Unexpand();
5763 else
5764 m_selected_row->Expand();
5765 }
5766 return eKeyHandled;
5767
5768 case 'h':
5769 window.CreateHelpSubwindow();
5770 return eKeyHandled;
5771
5772 default:
5773 break;
5774 }
5775 return eKeyNotHandled;
5776 }
5777
5778protected:
5779 std::vector<Row> m_rows;
5780 Row *m_selected_row = nullptr;
5781 uint32_t m_selected_row_idx = 0;
5782 uint32_t m_first_visible_row = 0;
5783 uint32_t m_num_rows = 0;
5784 int m_min_x = 0;
5785 int m_min_y = 0;
5786 int m_max_x = 0;
5787 int m_max_y = 0;
5788
5789 static Format FormatForChar(int c) {
5790 switch (c) {
5791 case 'x':
5792 return eFormatHex;
5793 case 'X':
5794 return eFormatHexUppercase;
5795 case 'o':
5796 return eFormatOctal;
5797 case 's':
5798 return eFormatCString;
5799 case 'u':
5800 return eFormatUnsigned;
5801 case 'd':
5802 return eFormatDecimal;
5803 case 'D':
5804 return eFormatDefault;
5805 case 'i':
5806 return eFormatInstruction;
5807 case 'A':
5808 return eFormatAddressInfo;
5809 case 'p':
5810 return eFormatPointer;
5811 case 'c':
5812 return eFormatChar;
5813 case 'b':
5814 return eFormatBinary;
5815 case 'B':
5816 return eFormatBytesWithASCII;
5817 case 'f':
5818 return eFormatFloat;
5819 }
5820 return eFormatDefault;
5821 }
5822
5823 bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options,
5824 bool highlight, bool last_child) {
5825 ValueObject *valobj = row.value.GetSP().get();
5826
5827 if (valobj == nullptr)
5828 return false;
5829
5830 const char *type_name =
5831 options.show_types ? valobj->GetTypeName().GetCString() : nullptr;
5832 const char *name = valobj->GetName().GetCString();
5833 const char *value = valobj->GetValueAsCString();
5834 const char *summary = valobj->GetSummaryAsCString();
5835
5836 window.MoveCursor(row.x, row.y);
5837
5838 row.DrawTree(window);
5839
5840 if (highlight)
5841 window.AttributeOn(A_REVERSE);
5842
5843 if (type_name && type_name[0])
5844 window.PrintfTruncated(1, "(%s) ", type_name);
5845
5846 if (name && name[0])
5847 window.PutCStringTruncated(1, name);
5848
5849 attr_t changd_attr = 0;
5850 if (valobj->GetValueDidChange())
5851 changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD;
5852
5853 if (value && value[0]) {
5854 window.PutCStringTruncated(1, " = ");
5855 if (changd_attr)
5856 window.AttributeOn(changd_attr);
5857 window.PutCStringTruncated(1, value);
5858 if (changd_attr)
5859 window.AttributeOff(changd_attr);
5860 }
5861
5862 if (summary && summary[0]) {
5863 window.PutCStringTruncated(1, " ");
5864 if (changd_attr)
5865 window.AttributeOn(changd_attr);
5866 window.PutCStringTruncated(1, summary);
5867 if (changd_attr)
5868 window.AttributeOff(changd_attr);
5869 }
5870
5871 if (highlight)
5872 window.AttributeOff(A_REVERSE);
5873
5874 return true;
5875 }
5876
5877 void DisplayRows(Window &window, std::vector<Row> &rows,
5878 DisplayOptions &options) {
5879 // > 0x25B7
5880 // \/ 0x25BD
5881
5882 bool window_is_active = window.IsActive();
5883 for (auto &row : rows) {
5884 const bool last_child = row.parent && &rows[rows.size() - 1] == &row;
5885 // Save the row index in each Row structure
5886 row.row_idx = m_num_rows;
5887 if ((m_num_rows >= m_first_visible_row) &&
5888 ((m_num_rows - m_first_visible_row) <
5889 static_cast<size_t>(NumVisibleRows()))) {
5890 row.x = m_min_x;
5891 row.y = m_num_rows - m_first_visible_row + 1;
5892 if (DisplayRowObject(window, row, options,
5893 window_is_active &&
5894 m_num_rows == m_selected_row_idx,
5895 last_child)) {
5896 ++m_num_rows;
5897 } else {
5898 row.x = 0;
5899 row.y = 0;
5900 }
5901 } else {
5902 row.x = 0;
5903 row.y = 0;
5904 ++m_num_rows;
5905 }
5906
5907 if (row.expanded) {
5908 auto &children = row.GetChildren();
5909 if (!children.empty()) {
5910 DisplayRows(window, children, options);
5911 }
5912 }
5913 }
5914 }
5915
5916 int CalculateTotalNumberRows(std::vector<Row> &rows) {
5917 int row_count = 0;
5918 for (auto &row : rows) {
5919 ++row_count;
5920 if (row.expanded)
5921 row_count += CalculateTotalNumberRows(row.GetChildren());
5922 }
5923 return row_count;
5924 }
5925
5926 static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) {
5927 for (auto &row : rows) {
5928 if (row_index == 0)
5929 return &row;
5930 else {
5931 --row_index;
5932 if (row.expanded) {
5933 auto &children = row.GetChildren();
5934 if (!children.empty()) {
5935 Row *result = GetRowForRowIndexImpl(children, row_index);
5936 if (result)
5937 return result;
5938 }
5939 }
5940 }
5941 }
5942 return nullptr;
5943 }
5944
5945 Row *GetRowForRowIndex(size_t row_index) {
5946 return GetRowForRowIndexImpl(m_rows, row_index);
5947 }
5948
5949 int NumVisibleRows() const { return m_max_y - m_min_y; }
5950
5951 static DisplayOptions g_options;
5952};
5953
5954class FrameVariablesWindowDelegate : public ValueObjectListDelegate {
5955public:
5956 FrameVariablesWindowDelegate(Debugger &debugger)
5957 : ValueObjectListDelegate(), m_debugger(debugger) {}
5958
5959 ~FrameVariablesWindowDelegate() override = default;
5960
5961 const char *WindowDelegateGetHelpText() override {
5962 return "Frame variable window keyboard shortcuts:";
5963 }
5964
5965 bool WindowDelegateDraw(Window &window, bool force) override {
5966 ExecutionContext exe_ctx(
5967 m_debugger.GetCommandInterpreter().GetExecutionContext());
5968 Process *process = exe_ctx.GetProcessPtr();
5969 Block *frame_block = nullptr;
5970 StackFrame *frame = nullptr;
5971
5972 if (process) {
5973 StateType state = process->GetState();
5974 if (StateIsStoppedState(state, true)) {
5975 frame = exe_ctx.GetFramePtr();
5976 if (frame)
5977 frame_block = frame->GetFrameBlock();
5978 } else if (StateIsRunningState(state)) {
5979 return true; // Don't do any updating when we are running
5980 }
5981 }
5982
5983 ValueObjectList local_values;
5984 if (frame_block) {
5985 // Only update the variables if they have changed
5986 if (m_frame_block != frame_block) {
5987 m_frame_block = frame_block;
5988
5989 VariableList *locals = frame->GetVariableList(
5990 /*get_file_globals=*/true, /*include_synthetic_vars=*/true,
5991 nullptr);
5992 if (locals) {
5993 const DynamicValueType use_dynamic = eDynamicDontRunTarget;
5994 for (const VariableSP &local_sp : *locals) {
5995 ValueObjectSP value_sp =
5996 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic);
5997 if (value_sp) {
5998 ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue();
5999 if (synthetic_value_sp)
6000 local_values.Append(synthetic_value_sp);
6001 else
6002 local_values.Append(value_sp);
6003 }
6004 }
6005 // Update the values
6006 SetValues(local_values);
6007 }
6008 }
6009 } else {
6010 m_frame_block = nullptr;
6011 // Update the values with an empty list if there is no frame
6012 SetValues(local_values);
6013 }
6014
6015 return ValueObjectListDelegate::WindowDelegateDraw(window, force);
6016 }
6017
6018protected:
6019 Debugger &m_debugger;
6020 Block *m_frame_block = nullptr;
6021};
6022
6023class RegistersWindowDelegate : public ValueObjectListDelegate {
6024public:
6025 RegistersWindowDelegate(Debugger &debugger)
6026 : ValueObjectListDelegate(), m_debugger(debugger) {}
6027
6028 ~RegistersWindowDelegate() override = default;
6029
6030 const char *WindowDelegateGetHelpText() override {
6031 return "Register window keyboard shortcuts:";
6032 }
6033
6034 bool WindowDelegateDraw(Window &window, bool force) override {
6035 ExecutionContext exe_ctx(
6036 m_debugger.GetCommandInterpreter().GetExecutionContext());
6037 StackFrame *frame = exe_ctx.GetFramePtr();
6038
6039 ValueObjectList value_list;
6040 if (frame) {
6041 if (frame->GetStackID() != m_stack_id) {
6042 m_stack_id = frame->GetStackID();
6043 RegisterContextSP reg_ctx(frame->GetRegisterContext());
6044 if (reg_ctx) {
6045 const uint32_t num_sets = reg_ctx->GetRegisterSetCount();
6046 for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) {
6047 value_list.Append(
6048 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx));
6049 }
6050 }
6051 SetValues(value_list);
6052 }
6053 } else {
6054 Process *process = exe_ctx.GetProcessPtr();
6055 if (process && process->IsAlive())
6056 return true; // Don't do any updating if we are running
6057 else {
6058 // Update the values with an empty list if there is no process or the
6059 // process isn't alive anymore
6060 SetValues(value_list);
6061 }
6062 }
6063 return ValueObjectListDelegate::WindowDelegateDraw(window, force);
6064 }
6065
6066protected:
6067 Debugger &m_debugger;
6068 StackID m_stack_id;
6069};
6070
6071static const char *CursesKeyToCString(int ch) {
6072 static char g_desc[32];
6073 if (ch >= KEY_F0 && ch < KEY_F0 + 64) {
6074 snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0);
6075 return g_desc;
6076 }
6077 switch (ch) {
6078 case KEY_DOWN:
6079 return "down";
6080 case KEY_UP:
6081 return "up";
6082 case KEY_LEFT:
6083 return "left";
6084 case KEY_RIGHT:
6085 return "right";
6086 case KEY_HOME:
6087 return "home";
6088 case KEY_BACKSPACE:
6089 return "backspace";
6090 case KEY_DL:
6091 return "delete-line";
6092 case KEY_IL:
6093 return "insert-line";
6094 case KEY_DC:
6095 return "delete-char";
6096 case KEY_IC:
6097 return "insert-char";
6098 case KEY_CLEAR:
6099 return "clear";
6100 case KEY_EOS:
6101 return "clear-to-eos";
6102 case KEY_EOL:
6103 return "clear-to-eol";
6104 case KEY_SF:
6105 return "scroll-forward";
6106 case KEY_SR:
6107 return "scroll-backward";
6108 case KEY_NPAGE:
6109 return "page-down";
6110 case KEY_PPAGE:
6111 return "page-up";
6112 case KEY_STAB:
6113 return "set-tab";
6114 case KEY_CTAB:
6115 return "clear-tab";
6116 case KEY_CATAB:
6117 return "clear-all-tabs";
6118 case KEY_ENTER:
6119 return "enter";
6120 case KEY_PRINT:
6121 return "print";
6122 case KEY_LL:
6123 return "lower-left key";
6124 case KEY_A1:
6125 return "upper left of keypad";
6126 case KEY_A3:
6127 return "upper right of keypad";
6128 case KEY_B2:
6129 return "center of keypad";
6130 case KEY_C1:
6131 return "lower left of keypad";
6132 case KEY_C3:
6133 return "lower right of keypad";
6134 case KEY_BTAB:
6135 return "back-tab key";
6136 case KEY_BEG:
6137 return "begin key";
6138 case KEY_CANCEL:
6139 return "cancel key";
6140 case KEY_CLOSE:
6141 return "close key";
6142 case KEY_COMMAND:
6143 return "command key";
6144 case KEY_COPY:
6145 return "copy key";
6146 case KEY_CREATE:
6147 return "create key";
6148 case KEY_END:
6149 return "end key";
6150 case KEY_EXIT:
6151 return "exit key";
6152 case KEY_FIND:
6153 return "find key";
6154 case KEY_HELP:
6155 return "help key";
6156 case KEY_MARK:
6157 return "mark key";
6158 case KEY_MESSAGE:
6159 return "message key";
6160 case KEY_MOVE:
6161 return "move key";
6162 case KEY_NEXT:
6163 return "next key";
6164 case KEY_OPEN:
6165 return "open key";
6166 case KEY_OPTIONS:
6167 return "options key";
6168 case KEY_PREVIOUS:
6169 return "previous key";
6170 case KEY_REDO:
6171 return "redo key";
6172 case KEY_REFERENCE:
6173 return "reference key";
6174 case KEY_REFRESH:
6175 return "refresh key";
6176 case KEY_REPLACE:
6177 return "replace key";
6178 case KEY_RESTART:
6179 return "restart key";
6180 case KEY_RESUME:
6181 return "resume key";
6182 case KEY_SAVE:
6183 return "save key";
6184 case KEY_SBEG:
6185 return "shifted begin key";
6186 case KEY_SCANCEL:
6187 return "shifted cancel key";
6188 case KEY_SCOMMAND:
6189 return "shifted command key";
6190 case KEY_SCOPY:
6191 return "shifted copy key";
6192 case KEY_SCREATE:
6193 return "shifted create key";
6194 case KEY_SDC:
6195 return "shifted delete-character key";
6196 case KEY_SDL:
6197 return "shifted delete-line key";
6198 case KEY_SELECT:
6199 return "select key";
6200 case KEY_SEND:
6201 return "shifted end key";
6202 case KEY_SEOL:
6203 return "shifted clear-to-end-of-line key";
6204 case KEY_SEXIT:
6205 return "shifted exit key";
6206 case KEY_SFIND:
6207 return "shifted find key";
6208 case KEY_SHELP:
6209 return "shifted help key";
6210 case KEY_SHOME:
6211 return "shifted home key";
6212 case KEY_SIC:
6213 return "shifted insert-character key";
6214 case KEY_SLEFT:
6215 return "shifted left-arrow key";
6216 case KEY_SMESSAGE:
6217 return "shifted message key";
6218 case KEY_SMOVE:
6219 return "shifted move key";
6220 case KEY_SNEXT:
6221 return "shifted next key";
6222 case KEY_SOPTIONS:
6223 return "shifted options key";
6224 case KEY_SPREVIOUS:
6225 return "shifted previous key";
6226 case KEY_SPRINT:
6227 return "shifted print key";
6228 case KEY_SREDO:
6229 return "shifted redo key";
6230 case KEY_SREPLACE:
6231 return "shifted replace key";
6232 case KEY_SRIGHT:
6233 return "shifted right-arrow key";
6234 case KEY_SRSUME:
6235 return "shifted resume key";
6236 case KEY_SSAVE:
6237 return "shifted save key";
6238 case KEY_SSUSPEND:
6239 return "shifted suspend key";
6240 case KEY_SUNDO:
6241 return "shifted undo key";
6242 case KEY_SUSPEND:
6243 return "suspend key";
6244 case KEY_UNDO:
6245 return "undo key";
6246 case KEY_MOUSE:
6247 return "Mouse event has occurred";
6248 case KEY_RESIZE:
6249 return "Terminal resize event";
6250#ifdef KEY_EVENT
6251 case KEY_EVENT:
6252 return "We were interrupted by an event";
6253#endif
6254 case KEY_RETURN:
6255 return "return";
6256 case ' ':
6257 return "space";
6258 case '\t':
6259 return "tab";
6260 case KEY_ESCAPE:
6261 return "escape";
6262 default:
6263 if (llvm::isPrint(ch))
6264 snprintf(g_desc, sizeof(g_desc), "%c", ch);
6265 else
6266 snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch);
6267 return g_desc;
6268 }
6269 return nullptr;
6270}
6271
6272HelpDialogDelegate::HelpDialogDelegate(const char *text,
6273 KeyHelp *key_help_array)
6274 : m_text() {
6275 if (text && text[0]) {
6276 m_text.SplitIntoLines(text);
6277 m_text.AppendString("");
6278 }
6279 if (key_help_array) {
6280 for (KeyHelp *key = key_help_array; key->ch; ++key) {
6281 StreamString key_description;
6282 key_description.Printf("%10s - %s", CursesKeyToCString(key->ch),
6283 key->description);
6284 m_text.AppendString(key_description.GetString());
6285 }
6286 }
6287}
6288
6289HelpDialogDelegate::~HelpDialogDelegate() = default;
6290
6291bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) {
6292 window.Erase();
6293 const int window_height = window.GetHeight();
6294 int x = 2;
6295 int y = 1;
6296 const int min_y = y;
6297 const int max_y = window_height - 1 - y;
6298 const size_t num_visible_lines = max_y - min_y + 1;
6299 const size_t num_lines = m_text.GetSize();
6300 const char *bottom_message;
6301 if (num_lines <= num_visible_lines)
6302 bottom_message = "Press any key to exit";
6303 else
6304 bottom_message = "Use arrows to scroll, any other key to exit";
6305 window.DrawTitleBox(window.GetName(), bottom_message);
6306 while (y <= max_y) {
6307 window.MoveCursor(x, y);
6308 window.PutCStringTruncated(
6309 1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y));
6310 ++y;
6311 }
6312 return true;
6313}
6314
6315HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window,
6316 int key) {
6317 bool done = false;
6318 const size_t num_lines = m_text.GetSize();
6319 const size_t num_visible_lines = window.GetHeight() - 2;
6320
6321 if (num_lines <= num_visible_lines) {
6322 done = true;
6323 // If we have all lines visible and don't need scrolling, then any key
6324 // press will cause us to exit
6325 } else {
6326 switch (key) {
6327 case KEY_UP:
6328 if (m_first_visible_line > 0)
6329 --m_first_visible_line;
6330 break;
6331
6332 case KEY_DOWN:
6333 if (m_first_visible_line + num_visible_lines < num_lines)
6334 ++m_first_visible_line;
6335 break;
6336
6337 case KEY_PPAGE:
6338 case ',':
6339 if (m_first_visible_line > 0) {
6340 if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines)
6341 m_first_visible_line -= num_visible_lines;
6342 else
6343 m_first_visible_line = 0;
6344 }
6345 break;
6346
6347 case KEY_NPAGE:
6348 case '.':
6349 if (m_first_visible_line + num_visible_lines < num_lines) {
6350 m_first_visible_line += num_visible_lines;
6351 if (static_cast<size_t>(m_first_visible_line) > num_lines)
6352 m_first_visible_line = num_lines - num_visible_lines;
6353 }
6354 break;
6355
6356 default:
6357 done = true;
6358 break;
6359 }
6360 }
6361 if (done)
6362 window.GetParent()->RemoveSubWindow(&window);
6363 return eKeyHandled;
6364}
6365
6366class ApplicationDelegate : public WindowDelegate, public MenuDelegate {
6367public:
6368 enum {
6369 eMenuID_LLDB = 1,
6370 eMenuID_LLDBAbout,
6371 eMenuID_LLDBExit,
6372
6373 eMenuID_Target,
6374 eMenuID_TargetCreate,
6375 eMenuID_TargetDelete,
6376
6377 eMenuID_Process,
6378 eMenuID_ProcessAttach,
6379 eMenuID_ProcessDetachResume,
6380 eMenuID_ProcessDetachSuspended,
6381 eMenuID_ProcessLaunch,
6382 eMenuID_ProcessContinue,
6383 eMenuID_ProcessHalt,
6384 eMenuID_ProcessKill,
6385
6386 eMenuID_Thread,
6387 eMenuID_ThreadStepIn,
6388 eMenuID_ThreadStepOver,
6389 eMenuID_ThreadStepOut,
6390
6391 eMenuID_View,
6392 eMenuID_ViewBacktrace,
6393 eMenuID_ViewRegisters,
6394 eMenuID_ViewSource,
6395 eMenuID_ViewVariables,
6396 eMenuID_ViewBreakpoints,
6397
6398 eMenuID_Help,
6399 eMenuID_HelpGUIHelp
6400 };
6401
6402 ApplicationDelegate(Application &app, Debugger &debugger)
6403 : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {}
6404
6405 ~ApplicationDelegate() override = default;
6406
6407 bool WindowDelegateDraw(Window &window, bool force) override {
6408 return false; // Drawing not handled, let standard window drawing happen
6409 }
6410
6411 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
6412 switch (key) {
6413 case '\t':
6414 window.SelectNextWindowAsActive();
6415 return eKeyHandled;
6416
6417 case KEY_SHIFT_TAB:
6418 window.SelectPreviousWindowAsActive();
6419 return eKeyHandled;
6420
6421 case 'h':
6422 window.CreateHelpSubwindow();
6423 return eKeyHandled;
6424
6425 case KEY_ESCAPE:
6426 return eQuitApplication;
6427
6428 default:
6429 break;
6430 }
6431 return eKeyNotHandled;
6432 }
6433
6434 const char *WindowDelegateGetHelpText() override {
6435 return "Welcome to the LLDB curses GUI.\n\n"
6436 "Press the TAB key to change the selected view.\n"
6437 "Each view has its own keyboard shortcuts, press 'h' to open a "
6438 "dialog to display them.\n\n"
6439 "Common key bindings for all views:";
6440 }
6441
6442 KeyHelp *WindowDelegateGetKeyHelp() override {
6443 static curses::KeyHelp g_source_view_key_help[] = {
6444 {'\t', "Select next view"},
6445 {KEY_BTAB, "Select previous view"},
6446 {'h', "Show help dialog with view specific key bindings"},
6447 {',', "Page up"},
6448 {'.', "Page down"},
6449 {KEY_UP, "Select previous"},
6450 {KEY_DOWN, "Select next"},
6451 {KEY_LEFT, "Unexpand or select parent"},
6452 {KEY_RIGHT, "Expand"},
6453 {KEY_PPAGE, "Page up"},
6454 {KEY_NPAGE, "Page down"},
6455 {'\0', nullptr}};
6456 return g_source_view_key_help;
6457 }
6458
6459 MenuActionResult MenuDelegateAction(Menu &menu) override {
6460 switch (menu.GetIdentifier()) {
6461 case eMenuID_TargetCreate: {
6462 WindowSP main_window_sp = m_app.GetMainWindow();
6463 FormDelegateSP form_delegate_sp =
6464 FormDelegateSP(new TargetCreateFormDelegate(m_debugger));
6465 Rect bounds = main_window_sp->GetCenteredRect(80, 19);
6466 WindowSP form_window_sp = main_window_sp->CreateSubWindow(
6467 form_delegate_sp->GetName().c_str(), bounds, true);
6468 WindowDelegateSP window_delegate_sp =
6469 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
6470 form_window_sp->SetDelegate(window_delegate_sp);
6471 return MenuActionResult::Handled;
6472 }
6473 case eMenuID_ThreadStepIn: {
6474 ExecutionContext exe_ctx =
6475 m_debugger.GetCommandInterpreter().GetExecutionContext();
6476 if (exe_ctx.HasThreadScope()) {
6477 Process *process = exe_ctx.GetProcessPtr();
6478 if (process && process->IsAlive() &&
6479 StateIsStoppedState(process->GetState(), true))
6480 exe_ctx.GetThreadRef().StepIn(true);
6481 }
6482 }
6483 return MenuActionResult::Handled;
6484
6485 case eMenuID_ThreadStepOut: {
6486 ExecutionContext exe_ctx =
6487 m_debugger.GetCommandInterpreter().GetExecutionContext();
6488 if (exe_ctx.HasThreadScope()) {
6489 Process *process = exe_ctx.GetProcessPtr();
6490 if (process && process->IsAlive() &&
6491 StateIsStoppedState(process->GetState(), true)) {
6492 Thread *thread = exe_ctx.GetThreadPtr();
6493 uint32_t frame_idx =
6495 exe_ctx.GetThreadRef().StepOut(frame_idx);
6496 }
6497 }
6498 }
6499 return MenuActionResult::Handled;
6500
6501 case eMenuID_ThreadStepOver: {
6502 ExecutionContext exe_ctx =
6503 m_debugger.GetCommandInterpreter().GetExecutionContext();
6504 if (exe_ctx.HasThreadScope()) {
6505 Process *process = exe_ctx.GetProcessPtr();
6506 if (process && process->IsAlive() &&
6507 StateIsStoppedState(process->GetState(), true))
6508 exe_ctx.GetThreadRef().StepOver(true);
6509 }
6510 }
6511 return MenuActionResult::Handled;
6512
6513 case eMenuID_ProcessAttach: {
6514 WindowSP main_window_sp = m_app.GetMainWindow();
6515 FormDelegateSP form_delegate_sp = FormDelegateSP(
6516 new ProcessAttachFormDelegate(m_debugger, main_window_sp));
6517 Rect bounds = main_window_sp->GetCenteredRect(80, 22);
6518 WindowSP form_window_sp = main_window_sp->CreateSubWindow(
6519 form_delegate_sp->GetName().c_str(), bounds, true);
6520 WindowDelegateSP window_delegate_sp =
6521 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
6522 form_window_sp->SetDelegate(window_delegate_sp);
6523 return MenuActionResult::Handled;
6524 }
6525 case eMenuID_ProcessLaunch: {
6526 WindowSP main_window_sp = m_app.GetMainWindow();
6527 FormDelegateSP form_delegate_sp = FormDelegateSP(
6528 new ProcessLaunchFormDelegate(m_debugger, main_window_sp));
6529 Rect bounds = main_window_sp->GetCenteredRect(80, 22);
6530 WindowSP form_window_sp = main_window_sp->CreateSubWindow(
6531 form_delegate_sp->GetName().c_str(), bounds, true);
6532 WindowDelegateSP window_delegate_sp =
6533 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
6534 form_window_sp->SetDelegate(window_delegate_sp);
6535 return MenuActionResult::Handled;
6536 }
6537
6538 case eMenuID_ProcessContinue: {
6539 ExecutionContext exe_ctx =
6540 m_debugger.GetCommandInterpreter().GetExecutionContext();
6541 if (exe_ctx.HasProcessScope()) {
6542 Process *process = exe_ctx.GetProcessPtr();
6543 if (process && process->IsAlive() &&
6544 StateIsStoppedState(process->GetState(), true))
6545 process->Resume();
6546 }
6547 }
6548 return MenuActionResult::Handled;
6549
6550 case eMenuID_ProcessKill: {
6551 ExecutionContext exe_ctx =
6552 m_debugger.GetCommandInterpreter().GetExecutionContext();
6553 if (exe_ctx.HasProcessScope()) {
6554 Process *process = exe_ctx.GetProcessPtr();
6555 if (process && process->IsAlive())
6556 process->Destroy(false);
6557 }
6558 }
6559 return MenuActionResult::Handled;
6560
6561 case eMenuID_ProcessHalt: {
6562 ExecutionContext exe_ctx =
6563 m_debugger.GetCommandInterpreter().GetExecutionContext();
6564 if (exe_ctx.HasProcessScope()) {
6565 Process *process = exe_ctx.GetProcessPtr();
6566 if (process && process->IsAlive())
6567 process->Halt();
6568 }
6569 }
6570 return MenuActionResult::Handled;
6571
6572 case eMenuID_ProcessDetachResume:
6573 case eMenuID_ProcessDetachSuspended: {
6574 ExecutionContext exe_ctx =
6575 m_debugger.GetCommandInterpreter().GetExecutionContext();
6576 if (exe_ctx.HasProcessScope()) {
6577 Process *process = exe_ctx.GetProcessPtr();
6578 if (process && process->IsAlive())
6579 process->Detach(menu.GetIdentifier() ==
6580 eMenuID_ProcessDetachSuspended);
6581 }
6582 }
6583 return MenuActionResult::Handled;
6584
6585 case eMenuID_Process: {
6586 // Populate the menu with all of the threads if the process is stopped
6587 // when the Process menu gets selected and is about to display its
6588 // submenu.
6589 Menus &submenus = menu.GetSubmenus();
6590 ExecutionContext exe_ctx =
6591 m_debugger.GetCommandInterpreter().GetExecutionContext();
6592 Process *process = exe_ctx.GetProcessPtr();
6593 if (process && process->IsAlive() &&
6594 StateIsStoppedState(process->GetState(), true)) {
6595 if (submenus.size() == 7)
6596 menu.AddSubmenu(std::make_shared<Menu>(Menu::Type::Separator));
6597 else if (submenus.size() > 8)
6598 submenus.erase(submenus.begin() + 8, submenus.end());
6599
6600 ThreadList &threads = process->GetThreadList();
6601 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
6602 size_t num_threads = threads.GetSize();
6603 for (size_t i = 0; i < num_threads; ++i) {
6604 ThreadSP thread_sp = threads.GetThreadAtIndex(i);
6605 char menu_char = '\0';
6606 if (i < 9)
6607 menu_char = '1' + i;
6608 StreamString thread_menu_title;
6609 thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID());
6610 const char *thread_name = thread_sp->GetName();
6611 if (thread_name && thread_name[0])
6612 thread_menu_title.Printf(" %s", thread_name);
6613 else {
6614 const char *queue_name = thread_sp->GetQueueName();
6615 if (queue_name && queue_name[0])
6616 thread_menu_title.Printf(" %s", queue_name);
6617 }
6618 menu.AddSubmenu(std::make_shared<Menu>(
6619 thread_menu_title.GetString().str().c_str(), nullptr, menu_char,
6620 thread_sp->GetID()));
6621 }
6622 } else if (submenus.size() > 7) {
6623 // Remove the separator and any other thread submenu items that were
6624 // previously added
6625 submenus.erase(submenus.begin() + 7, submenus.end());
6626 }
6627 // Since we are adding and removing items we need to recalculate the
6628 // name lengths
6629 menu.RecalculateNameLengths();
6630 }
6631 return MenuActionResult::Handled;
6632
6633 case eMenuID_ViewVariables: {
6634 WindowSP main_window_sp = m_app.GetMainWindow();
6635 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
6636 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
6637 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
6638 const Rect source_bounds = source_window_sp->GetBounds();
6639
6640 if (variables_window_sp) {
6641 const Rect variables_bounds = variables_window_sp->GetBounds();
6642
6643 main_window_sp->RemoveSubWindow(variables_window_sp.get());
6644
6645 if (registers_window_sp) {
6646 // We have a registers window, so give all the area back to the
6647 // registers window
6648 Rect registers_bounds = variables_bounds;
6649 registers_bounds.size.width = source_bounds.size.width;
6650 registers_window_sp->SetBounds(registers_bounds);
6651 } else {
6652 // We have no registers window showing so give the bottom area back
6653 // to the source view
6654 source_window_sp->Resize(source_bounds.size.width,
6655 source_bounds.size.height +
6656 variables_bounds.size.height);
6657 }
6658 } else {
6659 Rect new_variables_rect;
6660 if (registers_window_sp) {
6661 // We have a registers window so split the area of the registers
6662 // window into two columns where the left hand side will be the
6663 // variables and the right hand side will be the registers
6664 const Rect variables_bounds = registers_window_sp->GetBounds();
6665 Rect new_registers_rect;
6666 variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect,
6667 new_registers_rect);
6668 registers_window_sp->SetBounds(new_registers_rect);
6669 } else {
6670 // No registers window, grab the bottom part of the source window
6671 Rect new_source_rect;
6672 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
6673 new_variables_rect);
6674 source_window_sp->SetBounds(new_source_rect);
6675 }
6676 WindowSP new_window_sp = main_window_sp->CreateSubWindow(
6677 "Variables", new_variables_rect, false);
6678 new_window_sp->SetDelegate(
6679 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
6680 }
6681 touchwin(stdscr);
6682 }
6683 return MenuActionResult::Handled;
6684
6685 case eMenuID_ViewRegisters: {
6686 WindowSP main_window_sp = m_app.GetMainWindow();
6687 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
6688 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
6689 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
6690 const Rect source_bounds = source_window_sp->GetBounds();
6691
6692 if (registers_window_sp) {
6693 if (variables_window_sp) {
6694 const Rect variables_bounds = variables_window_sp->GetBounds();
6695
6696 // We have a variables window, so give all the area back to the
6697 // variables window
6698 variables_window_sp->Resize(variables_bounds.size.width +
6699 registers_window_sp->GetWidth(),
6700 variables_bounds.size.height);
6701 } else {
6702 // We have no variables window showing so give the bottom area back
6703 // to the source view
6704 source_window_sp->Resize(source_bounds.size.width,
6705 source_bounds.size.height +
6706 registers_window_sp->GetHeight());
6707 }
6708 main_window_sp->RemoveSubWindow(registers_window_sp.get());
6709 } else {
6710 Rect new_regs_rect;
6711 if (variables_window_sp) {
6712 // We have a variables window, split it into two columns where the
6713 // left hand side will be the variables and the right hand side will
6714 // be the registers
6715 const Rect variables_bounds = variables_window_sp->GetBounds();
6716 Rect new_vars_rect;
6717 variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect,
6718 new_regs_rect);
6719 variables_window_sp->SetBounds(new_vars_rect);
6720 } else {
6721 // No variables window, grab the bottom part of the source window
6722 Rect new_source_rect;
6723 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
6724 new_regs_rect);
6725 source_window_sp->SetBounds(new_source_rect);
6726 }
6727 WindowSP new_window_sp =
6728 main_window_sp->CreateSubWindow("Registers", new_regs_rect, false);
6729 new_window_sp->SetDelegate(
6730 WindowDelegateSP(new RegistersWindowDelegate(m_debugger)));
6731 }
6732 touchwin(stdscr);
6733 }
6734 return MenuActionResult::Handled;
6735
6736 case eMenuID_ViewBreakpoints: {
6737 WindowSP main_window_sp = m_app.GetMainWindow();
6738 WindowSP threads_window_sp = main_window_sp->FindSubWindow("Threads");
6739 WindowSP breakpoints_window_sp =
6740 main_window_sp->FindSubWindow("Breakpoints");
6741 const Rect threads_bounds = threads_window_sp->GetBounds();
6742
6743 // If a breakpoints window already exists, remove it and give the area
6744 // it used to occupy to the threads window. If it doesn't exist, split
6745 // the threads window horizontally into two windows where the top window
6746 // is the threads window and the bottom window is a newly added
6747 // breakpoints window.
6748 if (breakpoints_window_sp) {
6749 threads_window_sp->Resize(threads_bounds.size.width,
6750 threads_bounds.size.height +
6751 breakpoints_window_sp->GetHeight());
6752 main_window_sp->RemoveSubWindow(breakpoints_window_sp.get());
6753 } else {
6754 Rect new_threads_bounds, breakpoints_bounds;
6755 threads_bounds.HorizontalSplitPercentage(0.70, new_threads_bounds,
6756 breakpoints_bounds);
6757 threads_window_sp->SetBounds(new_threads_bounds);
6758 breakpoints_window_sp = main_window_sp->CreateSubWindow(
6759 "Breakpoints", breakpoints_bounds, false);
6760 TreeDelegateSP breakpoints_delegate_sp(
6761 new BreakpointsTreeDelegate(m_debugger));
6762 breakpoints_window_sp->SetDelegate(WindowDelegateSP(
6763 new TreeWindowDelegate(m_debugger, breakpoints_delegate_sp)));
6764 }
6765 touchwin(stdscr);
6766 return MenuActionResult::Handled;
6767 }
6768
6769 case eMenuID_HelpGUIHelp:
6770 m_app.GetMainWindow()->CreateHelpSubwindow();
6771 return MenuActionResult::Handled;
6772
6773 default:
6774 break;
6775 }
6776
6777 return MenuActionResult::NotHandled;
6778 }
6779
6780protected:
6781 Application &m_app;
6782 Debugger &m_debugger;
6783};
6784
6785class StatusBarWindowDelegate : public WindowDelegate {
6786public:
6787 StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) {
6788 FormatEntity::Parse("Thread: ${thread.id%tid}", m_format);
6789 }
6790
6791 ~StatusBarWindowDelegate() override = default;
6792
6793 bool WindowDelegateDraw(Window &window, bool force) override {
6794 ExecutionContext exe_ctx =
6795 m_debugger.GetCommandInterpreter().GetExecutionContext();
6796 Process *process = exe_ctx.GetProcessPtr();
6797 Thread *thread = exe_ctx.GetThreadPtr();
6798 StackFrame *frame = exe_ctx.GetFramePtr();
6799 window.Erase();
6800 window.SetBackground(BlackOnWhite);
6801 window.MoveCursor(0, 0);
6802 if (process) {
6803 const StateType state = process->GetState();
6804 window.Printf("Process: %5" PRIu64 " %10s", process->GetID(),
6805 StateAsCString(state));
6806
6807 if (StateIsStoppedState(state, true)) {
6808 StreamString strm;
6809 if (thread &&
6810 FormatEntity::Formatter(nullptr, &exe_ctx, nullptr, false, false)
6811 .Format(m_format, strm)) {
6812 window.MoveCursor(40, 0);
6813 window.PutCStringTruncated(1, strm.GetString().str().c_str());
6814 }
6815
6816 window.MoveCursor(60, 0);
6817 if (frame)
6818 window.Printf("Frame: %3u PC = 0x%16.16" PRIx64,
6819 frame->GetFrameIndex(),
6821 exe_ctx.GetTargetPtr()));
6822 } else if (state == eStateExited) {
6823 const char *exit_desc = process->GetExitDescription();
6824 const int exit_status = process->GetExitStatus();
6825 if (exit_desc && exit_desc[0])
6826 window.Printf(" with status = %i (%s)", exit_status, exit_desc);
6827 else
6828 window.Printf(" with status = %i", exit_status);
6829 }
6830 }
6831 return true;
6832 }
6833
6834protected:
6835 Debugger &m_debugger;
6836 FormatEntity::Entry m_format;
6837};
6838
6839class SourceFileWindowDelegate : public WindowDelegate {
6840public:
6841 SourceFileWindowDelegate(Debugger &debugger)
6842 : WindowDelegate(), m_debugger(debugger) {}
6843
6844 ~SourceFileWindowDelegate() override = default;
6845
6846 void Update(const SymbolContext &sc) { m_sc = sc; }
6847
6848 uint32_t NumVisibleLines() const { return m_max_y - m_min_y; }
6849
6850 const char *WindowDelegateGetHelpText() override {
6851 return "Source/Disassembly window keyboard shortcuts:";
6852 }
6853
6854 KeyHelp *WindowDelegateGetKeyHelp() override {
6855 static curses::KeyHelp g_source_view_key_help[] = {
6856 {KEY_RETURN, "Run to selected line with one shot breakpoint"},
6857 {KEY_UP, "Select previous source line"},
6858 {KEY_DOWN, "Select next source line"},
6859 {KEY_LEFT, "Scroll to the left"},
6860 {KEY_RIGHT, "Scroll to the right"},
6861 {KEY_PPAGE, "Page up"},
6862 {KEY_NPAGE, "Page down"},
6863 {'b', "Set breakpoint on selected source/disassembly line"},
6864 {'c', "Continue process"},
6865 {'D', "Detach with process suspended"},
6866 {'h', "Show help dialog"},
6867 {'n', "Step over (source line)"},
6868 {'N', "Step over (single instruction)"},
6869 {'f', "Step out (finish)"},
6870 {'s', "Step in (source line)"},
6871 {'S', "Step in (single instruction)"},
6872 {'u', "Frame up"},
6873 {'d', "Frame down"},
6874 {',', "Page up"},
6875 {'.', "Page down"},
6876 {'\0', nullptr}};
6877 return g_source_view_key_help;
6878 }
6879
6880 bool WindowDelegateDraw(Window &window, bool force) override {
6881 ExecutionContext exe_ctx =
6882 m_debugger.GetCommandInterpreter().GetExecutionContext();
6883 Process *process = exe_ctx.GetProcessPtr();
6884 Thread *thread = nullptr;
6885
6886 bool update_location = false;
6887 if (process) {
6888 StateType state = process->GetState();
6889 if (StateIsStoppedState(state, true)) {
6890 // We are stopped, so it is ok to
6891 update_location = true;
6892 }
6893 }
6894
6895 m_min_x = 1;
6896 m_min_y = 2;
6897 m_max_x = window.GetMaxX() - 1;
6898 m_max_y = window.GetMaxY() - 1;
6899
6900 const uint32_t num_visible_lines = NumVisibleLines();
6901 StackFrameSP frame_sp;
6902 bool set_selected_line_to_pc = false;
6903
6904 if (update_location) {
6905 const bool process_alive = process->IsAlive();
6906 bool thread_changed = false;
6907 if (process_alive) {
6908 thread = exe_ctx.GetThreadPtr();
6909 if (thread) {
6910 frame_sp = thread->GetSelectedFrame(SelectMostRelevantFrame);
6911 auto tid = thread->GetID();
6912 thread_changed = tid != m_tid;
6913 m_tid = tid;
6914 } else {
6915 if (m_tid != LLDB_INVALID_THREAD_ID) {
6916 thread_changed = true;
6917 m_tid = LLDB_INVALID_THREAD_ID;
6918 }
6919 }
6920 }
6921 const uint32_t stop_id = process ? process->GetStopID() : 0;
6922 const bool stop_id_changed = stop_id != m_stop_id;
6923 bool frame_changed = false;
6924 m_stop_id = stop_id;
6925 m_title.Clear();
6926 if (frame_sp) {
6927 m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything);
6928 if (m_sc.module_sp) {
6929 m_title.Printf(
6930 "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString());
6931 ConstString func_name = m_sc.GetFunctionName();
6932 if (func_name)
6933 m_title.Printf("`%s", func_name.GetCString());
6934 }
6935 const uint32_t frame_idx = frame_sp->GetFrameIndex();
6936 frame_changed = frame_idx != m_frame_idx;
6937 m_frame_idx = frame_idx;
6938 } else {
6939 m_sc.Clear(true);
6940 frame_changed = m_frame_idx != UINT32_MAX;
6941 m_frame_idx = UINT32_MAX;
6942 }
6943
6944 const bool context_changed =
6945 thread_changed || frame_changed || stop_id_changed;
6946
6947 if (process_alive) {
6948 if (m_sc.line_entry.IsValid()) {
6949 m_pc_line = m_sc.line_entry.line;
6950 if (m_pc_line != UINT32_MAX)
6951 --m_pc_line; // Convert to zero based line number...
6952 // Update the selected line if the stop ID changed...
6953 if (context_changed)
6954 m_selected_line = m_pc_line;
6955
6956 if (m_file_sp && m_file_sp->GetSupportFile()->GetSpecOnly() ==
6957 m_sc.line_entry.GetFile()) {
6958 // Same file, nothing to do, we should either have the lines or
6959 // not (source file missing)
6960 if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) {
6961 if (m_selected_line >= m_first_visible_line + num_visible_lines)
6962 m_first_visible_line = m_selected_line - 10;
6963 } else {
6964 if (m_selected_line > 10)
6965 m_first_visible_line = m_selected_line - 10;
6966 else
6967 m_first_visible_line = 0;
6968 }
6969 } else {
6970 // File changed, set selected line to the line with the PC
6971 m_selected_line = m_pc_line;
6972 m_file_sp =
6973 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file_sp);
6974 if (m_file_sp) {
6975 const size_t num_lines = m_file_sp->GetNumLines();
6976 m_line_width = 1;
6977 for (size_t n = num_lines; n >= 10; n = n / 10)
6978 ++m_line_width;
6979
6980 if (num_lines < num_visible_lines ||
6981 m_selected_line < num_visible_lines)
6982 m_first_visible_line = 0;
6983 else
6984 m_first_visible_line = m_selected_line - 10;
6985 }
6986 }
6987 } else {
6988 m_file_sp.reset();
6989 }
6990
6991 if (!m_file_sp || m_file_sp->GetNumLines() == 0) {
6992 // Show disassembly
6993 bool prefer_file_cache = false;
6994 if (m_sc.function) {
6995 if (m_disassembly_scope != m_sc.function) {
6996 m_disassembly_scope = m_sc.function;
6997 m_disassembly_sp = m_sc.function->GetInstructions(
6998 exe_ctx, nullptr, !prefer_file_cache);
6999 if (m_disassembly_sp)
7000 set_selected_line_to_pc = true;
7001 } else {
7002 set_selected_line_to_pc = context_changed;
7003 }
7004 } else if (m_sc.symbol) {
7005 if (m_disassembly_scope != m_sc.symbol) {
7006 m_disassembly_scope = m_sc.symbol;
7007 m_disassembly_sp = m_sc.symbol->GetInstructions(
7008 exe_ctx, nullptr, prefer_file_cache);
7009 if (m_disassembly_sp)
7010 set_selected_line_to_pc = true;
7011 } else {
7012 set_selected_line_to_pc = context_changed;
7013 }
7014 }
7015 }
7016 } else {
7017 m_pc_line = UINT32_MAX;
7018 }
7019 }
7020
7021 const int window_width = window.GetWidth();
7022 window.Erase();
7023 window.DrawTitleBox("Sources");
7024 if (!m_title.GetString().empty()) {
7025 window.AttributeOn(A_REVERSE);
7026 window.MoveCursor(1, 1);
7027 window.PutChar(' ');
7028 window.PutCStringTruncated(1, m_title.GetString().str().c_str());
7029 int x = window.GetCursorX();
7030 if (x < window_width - 1) {
7031 window.Printf("%*s", window_width - x - 1, "");
7032 }
7033 window.AttributeOff(A_REVERSE);
7034 }
7035
7036 Target *target = exe_ctx.GetTargetPtr();
7037 const size_t num_source_lines = GetNumSourceLines();
7038 if (num_source_lines > 0) {
7039 // Display source
7040 BreakpointLines bp_lines;
7041 if (target) {
7042 BreakpointList &bp_list = target->GetBreakpointList();
7043 const size_t num_bps = bp_list.GetSize();
7044 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7045 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7046 const size_t num_bps_locs = bp_sp->GetNumLocations();
7047 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
7048 BreakpointLocationSP bp_loc_sp =
7049 bp_sp->GetLocationAtIndex(bp_loc_idx);
7050 LineEntry bp_loc_line_entry;
7051 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
7052 bp_loc_line_entry)) {
7053 if (m_file_sp->GetSupportFile()->GetSpecOnly() ==
7054 bp_loc_line_entry.GetFile()) {
7055 bp_lines.insert(bp_loc_line_entry.line);
7056 }
7057 }
7058 }
7059 }
7060 }
7061
7062 for (size_t i = 0; i < num_visible_lines; ++i) {
7063 const uint32_t curr_line = m_first_visible_line + i;
7064 if (curr_line < num_source_lines) {
7065 const int line_y = m_min_y + i;
7066 window.MoveCursor(1, line_y);
7067 const bool is_pc_line = curr_line == m_pc_line;
7068 const bool line_is_selected = m_selected_line == curr_line;
7069 // Highlight the line as the PC line first (done by passing
7070 // argument to OutputColoredStringTruncated()), then if the selected
7071 // line isn't the same as the PC line, highlight it differently.
7072 attr_t highlight_attr = 0;
7073 attr_t bp_attr = 0;
7074 if (line_is_selected && !is_pc_line)
7075 highlight_attr = A_REVERSE;
7076
7077 if (bp_lines.find(curr_line + 1) != bp_lines.end())
7078 bp_attr = COLOR_PAIR(BlackOnWhite);
7079
7080 if (bp_attr)
7081 window.AttributeOn(bp_attr);
7082
7083 window.Printf(" %*u ", m_line_width, curr_line + 1);
7084
7085 if (bp_attr)
7086 window.AttributeOff(bp_attr);
7087
7088 window.PutChar(ACS_VLINE);
7089 // Mark the line with the PC with a diamond
7090 if (is_pc_line)
7091 window.PutChar(ACS_DIAMOND);
7092 else
7093 window.PutChar(' ');
7094
7095 if (highlight_attr)
7096 window.AttributeOn(highlight_attr);
7097
7098 StreamString lineStream;
7099
7100 std::optional<size_t> column;
7101 if (is_pc_line && m_sc.line_entry.IsValid() && m_sc.line_entry.column)
7102 column = m_sc.line_entry.column - 1;
7103 m_file_sp->DisplaySourceLines(curr_line + 1, column, 0, 0,
7104 &lineStream);
7105 StringRef line = lineStream.GetString();
7106 if (line.ends_with("\n"))
7107 line = line.drop_back();
7108 bool wasWritten = window.OutputColoredStringTruncated(
7109 1, line, m_first_visible_column, is_pc_line);
7110 if (!wasWritten && (line_is_selected || is_pc_line)) {
7111 // Draw an empty space to show the selected/PC line if empty,
7112 // or draw '<' if nothing is visible because of scrolling too much
7113 // to the right.
7114 window.PutCStringTruncated(
7115 1, line.empty() && m_first_visible_column == 0 ? " " : "<");
7116 }
7117
7118 if (is_pc_line && frame_sp &&
7119 frame_sp->GetConcreteFrameIndex() == 0) {
7120 StopInfoSP stop_info_sp;
7121 if (thread)
7122 stop_info_sp = thread->GetStopInfo();
7123 if (stop_info_sp) {
7124 const char *stop_description = stop_info_sp->GetDescription();
7125 if (stop_description && stop_description[0]) {
7126 size_t stop_description_len = strlen(stop_description);
7127 int desc_x = window_width - stop_description_len - 16;
7128 if (desc_x - window.GetCursorX() > 0)
7129 window.Printf("%*s", desc_x - window.GetCursorX(), "");
7130 window.MoveCursor(window_width - stop_description_len - 16,
7131 line_y);
7132 const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue);
7133 window.AttributeOn(stop_reason_attr);
7134 window.PrintfTruncated(1, " <<< Thread %u: %s ",
7135 thread->GetIndexID(), stop_description);
7136 window.AttributeOff(stop_reason_attr);
7137 }
7138 } else {
7139 window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
7140 }
7141 }
7142 if (highlight_attr)
7143 window.AttributeOff(highlight_attr);
7144 } else {
7145 break;
7146 }
7147 }
7148 } else {
7149 size_t num_disassembly_lines = GetNumDisassemblyLines();
7150 if (num_disassembly_lines > 0) {
7151 // Display disassembly
7152 BreakpointAddrs bp_file_addrs;
7153 Target *target = exe_ctx.GetTargetPtr();
7154 if (target) {
7155 BreakpointList &bp_list = target->GetBreakpointList();
7156 const size_t num_bps = bp_list.GetSize();
7157 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7158 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7159 const size_t num_bps_locs = bp_sp->GetNumLocations();
7160 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs;
7161 ++bp_loc_idx) {
7162 BreakpointLocationSP bp_loc_sp =
7163 bp_sp->GetLocationAtIndex(bp_loc_idx);
7164 bp_file_addrs.insert(bp_loc_sp->GetAddress().GetFileAddress());
7165 }
7166 }
7167 }
7168
7169 const attr_t selected_highlight_attr = A_REVERSE;
7170 const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue);
7171
7172 StreamString strm;
7173
7174 InstructionList &insts = m_disassembly_sp->GetInstructionList();
7175 Address pc_address;
7176
7177 if (frame_sp)
7178 pc_address = frame_sp->GetFrameCodeAddress();
7179 const uint32_t pc_idx =
7180 pc_address.IsValid()
7181 ? insts.GetIndexOfInstructionAtAddress(pc_address)
7182 : UINT32_MAX;
7183 if (set_selected_line_to_pc) {
7184 m_selected_line = pc_idx;
7185 }
7186
7187 const uint32_t non_visible_pc_offset = (num_visible_lines / 5);
7188 if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines)
7189 m_first_visible_line = 0;
7190
7191 if (pc_idx < num_disassembly_lines) {
7192 if (pc_idx < static_cast<uint32_t>(m_first_visible_line) ||
7193 pc_idx >= m_first_visible_line + num_visible_lines)
7194 m_first_visible_line = pc_idx - non_visible_pc_offset;
7195 }
7196
7197 for (size_t i = 0; i < num_visible_lines; ++i) {
7198 const uint32_t inst_idx = m_first_visible_line + i;
7199 Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get();
7200 if (!inst)
7201 break;
7202
7203 const int line_y = m_min_y + i;
7204 window.MoveCursor(1, line_y);
7205 const bool is_pc_line = frame_sp && inst_idx == pc_idx;
7206 const bool line_is_selected = m_selected_line == inst_idx;
7207 // Highlight the line as the PC line first, then if the selected
7208 // line isn't the same as the PC line, highlight it differently
7209 attr_t highlight_attr = 0;
7210 attr_t bp_attr = 0;
7211 if (is_pc_line)
7212 highlight_attr = pc_highlight_attr;
7213 else if (line_is_selected)
7214 highlight_attr = selected_highlight_attr;
7215
7216 if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) !=
7217 bp_file_addrs.end())
7218 bp_attr = COLOR_PAIR(BlackOnWhite);
7219
7220 if (bp_attr)
7221 window.AttributeOn(bp_attr);
7222
7223 window.Printf(" 0x%16.16llx ",
7224 static_cast<unsigned long long>(
7225 inst->GetAddress().GetLoadAddress(target)));
7226
7227 if (bp_attr)
7228 window.AttributeOff(bp_attr);
7229
7230 window.PutChar(ACS_VLINE);
7231 // Mark the line with the PC with a diamond
7232 if (is_pc_line)
7233 window.PutChar(ACS_DIAMOND);
7234 else
7235 window.PutChar(' ');
7236
7237 if (highlight_attr)
7238 window.AttributeOn(highlight_attr);
7239
7240 const char *mnemonic = inst->GetMnemonic(&exe_ctx);
7241 const char *operands = inst->GetOperands(&exe_ctx);
7242 const char *comment = inst->GetComment(&exe_ctx);
7243
7244 if (mnemonic != nullptr && mnemonic[0] == '\0')
7245 mnemonic = nullptr;
7246 if (operands != nullptr && operands[0] == '\0')
7247 operands = nullptr;
7248 if (comment != nullptr && comment[0] == '\0')
7249 comment = nullptr;
7250
7251 strm.Clear();
7252
7253 if (mnemonic != nullptr && operands != nullptr && comment != nullptr)
7254 strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment);
7255 else if (mnemonic != nullptr && operands != nullptr)
7256 strm.Printf("%-8s %s", mnemonic, operands);
7257 else if (mnemonic != nullptr)
7258 strm.Printf("%s", mnemonic);
7259
7260 int right_pad = 1;
7261 window.PutCStringTruncated(
7262 right_pad,
7263 strm.GetString().substr(m_first_visible_column).data());
7264
7265 if (is_pc_line && frame_sp &&
7266 frame_sp->GetConcreteFrameIndex() == 0) {
7267 StopInfoSP stop_info_sp;
7268 if (thread)
7269 stop_info_sp = thread->GetStopInfo();
7270 if (stop_info_sp) {
7271 const char *stop_description = stop_info_sp->GetDescription();
7272 if (stop_description && stop_description[0]) {
7273 size_t stop_description_len = strlen(stop_description);
7274 int desc_x = window_width - stop_description_len - 16;
7275 if (desc_x - window.GetCursorX() > 0)
7276 window.Printf("%*s", desc_x - window.GetCursorX(), "");
7277 window.MoveCursor(window_width - stop_description_len - 15,
7278 line_y);
7279 if (thread)
7280 window.PrintfTruncated(1, "<<< Thread %u: %s ",
7281 thread->GetIndexID(),
7282 stop_description);
7283 }
7284 } else {
7285 window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
7286 }
7287 }
7288 if (highlight_attr)
7289 window.AttributeOff(highlight_attr);
7290 }
7291 }
7292 }
7293 return true; // Drawing handled
7294 }
7295
7296 size_t GetNumLines() {
7297 size_t num_lines = GetNumSourceLines();
7298 if (num_lines == 0)
7299 num_lines = GetNumDisassemblyLines();
7300 return num_lines;
7301 }
7302
7303 size_t GetNumSourceLines() const {
7304 if (m_file_sp)
7305 return m_file_sp->GetNumLines();
7306 return 0;
7307 }
7308
7309 size_t GetNumDisassemblyLines() const {
7310 if (m_disassembly_sp)
7311 return m_disassembly_sp->GetInstructionList().GetSize();
7312 return 0;
7313 }
7314
7315 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
7316 const uint32_t num_visible_lines = NumVisibleLines();
7317 const size_t num_lines = GetNumLines();
7318
7319 switch (c) {
7320 case ',':
7321 case KEY_PPAGE:
7322 // Page up key
7323 if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines)
7324 m_first_visible_line -= num_visible_lines;
7325 else
7326 m_first_visible_line = 0;
7327 m_selected_line = m_first_visible_line;
7328 return eKeyHandled;
7329
7330 case '.':
7331 case KEY_NPAGE:
7332 // Page down key
7333 {
7334 if (m_first_visible_line + num_visible_lines < num_lines)
7335 m_first_visible_line += num_visible_lines;
7336 else if (num_lines < num_visible_lines)
7337 m_first_visible_line = 0;
7338 else
7339 m_first_visible_line = num_lines - num_visible_lines;
7340 m_selected_line = m_first_visible_line;
7341 }
7342 return eKeyHandled;
7343
7344 case KEY_UP:
7345 if (m_selected_line > 0) {
7346 m_selected_line--;
7347 if (static_cast<size_t>(m_first_visible_line) > m_selected_line)
7348 m_first_visible_line = m_selected_line;
7349 }
7350 return eKeyHandled;
7351
7352 case KEY_DOWN:
7353 if (m_selected_line + 1 < num_lines) {
7354 m_selected_line++;
7355 if (m_first_visible_line + num_visible_lines < m_selected_line)
7356 m_first_visible_line++;
7357 }
7358 return eKeyHandled;
7359
7360 case KEY_LEFT:
7361 if (m_first_visible_column > 0)
7362 --m_first_visible_column;
7363 return eKeyHandled;
7364
7365 case KEY_RIGHT:
7366 ++m_first_visible_column;
7367 return eKeyHandled;
7368
7369 case '\r':
7370 case '\n':
7371 case KEY_ENTER:
7372 // Set a breakpoint and run to the line using a one shot breakpoint
7373 if (GetNumSourceLines() > 0) {
7374 ExecutionContext exe_ctx =
7375 m_debugger.GetCommandInterpreter().GetExecutionContext();
7376 if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) {
7377 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
7378 nullptr, // Don't limit the breakpoint to certain modules
7379 m_file_sp->GetSupportFile()->GetSpecOnly(), // Source file
7380 m_selected_line +
7381 1, // Source line number (m_selected_line is zero based)
7382 0, // Unspecified column.
7383 0, // No offset
7384 eLazyBoolCalculate, // Check inlines using global setting
7385 eLazyBoolCalculate, // Skip prologue using global setting,
7386 false, // internal
7387 false, // request_hardware
7388 eLazyBoolCalculate); // move_to_nearest_code
7389 // Make breakpoint one shot
7390 bp_sp->GetOptions().SetOneShot(true);
7391 exe_ctx.GetProcessRef().Resume();
7392 }
7393 } else if (m_selected_line < GetNumDisassemblyLines()) {
7394 const Instruction *inst = m_disassembly_sp->GetInstructionList()
7395 .GetInstructionAtIndex(m_selected_line)
7396 .get();
7397 ExecutionContext exe_ctx =
7398 m_debugger.GetCommandInterpreter().GetExecutionContext();
7399 if (exe_ctx.HasTargetScope()) {
7400 Address addr = inst->GetAddress();
7401 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
7402 addr, // lldb_private::Address
7403 false, // internal
7404 false); // request_hardware
7405 // Make breakpoint one shot
7406 bp_sp->GetOptions().SetOneShot(true);
7407 exe_ctx.GetProcessRef().Resume();
7408 }
7409 }
7410 return eKeyHandled;
7411
7412 case 'b': // 'b' == toggle breakpoint on currently selected line
7413 ToggleBreakpointOnSelectedLine();
7414 return eKeyHandled;
7415
7416 case 'D': // 'D' == detach and keep stopped
7417 {
7418 ExecutionContext exe_ctx =
7419 m_debugger.GetCommandInterpreter().GetExecutionContext();
7420 if (exe_ctx.HasProcessScope())
7421 exe_ctx.GetProcessRef().Detach(true);
7422 }
7423 return eKeyHandled;
7424
7425 case 'c':
7426 // 'c' == continue
7427 {
7428 ExecutionContext exe_ctx =
7429 m_debugger.GetCommandInterpreter().GetExecutionContext();
7430 if (exe_ctx.HasProcessScope())
7431 exe_ctx.GetProcessRef().Resume();
7432 }
7433 return eKeyHandled;
7434
7435 case 'f':
7436 // 'f' == step out (finish)
7437 {
7438 ExecutionContext exe_ctx =
7439 m_debugger.GetCommandInterpreter().GetExecutionContext();
7440 if (exe_ctx.HasThreadScope() &&
7441 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
7442 Thread *thread = exe_ctx.GetThreadPtr();
7443 uint32_t frame_idx =
7445 exe_ctx.GetThreadRef().StepOut(frame_idx);
7446 }
7447 }
7448 return eKeyHandled;
7449
7450 case 'n': // 'n' == step over
7451 case 'N': // 'N' == step over instruction
7452 {
7453 ExecutionContext exe_ctx =
7454 m_debugger.GetCommandInterpreter().GetExecutionContext();
7455 if (exe_ctx.HasThreadScope() &&
7456 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
7457 bool source_step = (c == 'n');
7458 exe_ctx.GetThreadRef().StepOver(source_step);
7459 }
7460 }
7461 return eKeyHandled;
7462
7463 case 's': // 's' == step into
7464 case 'S': // 'S' == step into instruction
7465 {
7466 ExecutionContext exe_ctx =
7467 m_debugger.GetCommandInterpreter().GetExecutionContext();
7468 if (exe_ctx.HasThreadScope() &&
7469 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
7470 bool source_step = (c == 's');
7471 exe_ctx.GetThreadRef().StepIn(source_step);
7472 }
7473 }
7474 return eKeyHandled;
7475
7476 case 'u': // 'u' == frame up
7477 case 'd': // 'd' == frame down
7478 {
7479 ExecutionContext exe_ctx =
7480 m_debugger.GetCommandInterpreter().GetExecutionContext();
7481 if (exe_ctx.HasThreadScope()) {
7482 Thread *thread = exe_ctx.GetThreadPtr();
7483 uint32_t frame_idx =
7485 if (frame_idx == UINT32_MAX)
7486 frame_idx = 0;
7487 if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount())
7488 ++frame_idx;
7489 else if (c == 'd' && frame_idx > 0)
7490 --frame_idx;
7491 if (thread->SetSelectedFrameByIndex(frame_idx, true))
7493 }
7494 }
7495 return eKeyHandled;
7496
7497 case 'h':
7498 window.CreateHelpSubwindow();
7499 return eKeyHandled;
7500
7501 default:
7502 break;
7503 }
7504 return eKeyNotHandled;
7505 }
7506
7507 void ToggleBreakpointOnSelectedLine() {
7508 ExecutionContext exe_ctx =
7509 m_debugger.GetCommandInterpreter().GetExecutionContext();
7510 if (!exe_ctx.HasTargetScope())
7511 return;
7512 if (GetNumSourceLines() > 0) {
7513 // Source file breakpoint.
7514 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
7515 const size_t num_bps = bp_list.GetSize();
7516 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7517 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7518 const size_t num_bps_locs = bp_sp->GetNumLocations();
7519 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
7520 BreakpointLocationSP bp_loc_sp =
7521 bp_sp->GetLocationAtIndex(bp_loc_idx);
7522 LineEntry bp_loc_line_entry;
7523 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
7524 bp_loc_line_entry)) {
7525 if (m_file_sp->GetSupportFile()->GetSpecOnly() ==
7526 bp_loc_line_entry.GetFile() &&
7527 m_selected_line + 1 == bp_loc_line_entry.line) {
7528 bool removed =
7529 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
7530 assert(removed);
7532 return; // Existing breakpoint removed.
7533 }
7534 }
7535 }
7536 }
7537 // No breakpoint found on the location, add it.
7538 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
7539 nullptr, // Don't limit the breakpoint to certain modules
7540 m_file_sp->GetSupportFile()->GetSpecOnly(), // Source file
7541 m_selected_line +
7542 1, // Source line number (m_selected_line is zero based)
7543 0, // No column specified.
7544 0, // No offset
7545 eLazyBoolCalculate, // Check inlines using global setting
7546 eLazyBoolCalculate, // Skip prologue using global setting,
7547 false, // internal
7548 false, // request_hardware
7549 eLazyBoolCalculate); // move_to_nearest_code
7550 } else {
7551 // Disassembly breakpoint.
7552 assert(GetNumDisassemblyLines() > 0);
7553 assert(m_selected_line < GetNumDisassemblyLines());
7554 const Instruction *inst = m_disassembly_sp->GetInstructionList()
7555 .GetInstructionAtIndex(m_selected_line)
7556 .get();
7557 Address addr = inst->GetAddress();
7558 // Try to find it.
7559 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
7560 const size_t num_bps = bp_list.GetSize();
7561 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7562 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7563 const size_t num_bps_locs = bp_sp->GetNumLocations();
7564 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
7565 BreakpointLocationSP bp_loc_sp =
7566 bp_sp->GetLocationAtIndex(bp_loc_idx);
7567 LineEntry bp_loc_line_entry;
7568 const lldb::addr_t file_addr =
7569 bp_loc_sp->GetAddress().GetFileAddress();
7570 if (file_addr == addr.GetFileAddress()) {
7571 bool removed =
7572 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
7573 assert(removed);
7575 return; // Existing breakpoint removed.
7576 }
7577 }
7578 }
7579 // No breakpoint found on the address, add it.
7580 BreakpointSP bp_sp =
7581 exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address
7582 false, // internal
7583 false); // request_hardware
7584 }
7585 }
7586
7587protected:
7588 typedef std::set<uint32_t> BreakpointLines;
7589 typedef std::set<lldb::addr_t> BreakpointAddrs;
7590
7591 Debugger &m_debugger;
7592 SymbolContext m_sc;
7593 SourceManager::FileSP m_file_sp;
7594 SymbolContextScope *m_disassembly_scope = nullptr;
7595 lldb::DisassemblerSP m_disassembly_sp;
7596 StreamString m_title;
7598 int m_line_width = 4;
7599 uint32_t m_selected_line = 0; // The selected line
7600 uint32_t m_pc_line = 0; // The line with the PC
7601 uint32_t m_stop_id = 0;
7602 uint32_t m_frame_idx = UINT32_MAX;
7603 int m_first_visible_line = 0;
7604 int m_first_visible_column = 0;
7605 int m_min_x = 0;
7606 int m_min_y = 0;
7607 int m_max_x = 0;
7608 int m_max_y = 0;
7609};
7610
7611DisplayOptions ValueObjectListDelegate::g_options = {true};
7612
7614 : IOHandler(debugger, IOHandler::Type::Curses) {}
7615
7618 if (!m_app_up) {
7619 m_app_up = std::make_unique<Application>(
7620 m_input_sp ? m_input_sp->GetStream() : nullptr,
7621 m_output_sp ? m_input_sp->GetStream() : nullptr);
7622
7623 // This is both a window and a menu delegate
7624 std::shared_ptr<ApplicationDelegate> app_delegate_sp(
7625 new ApplicationDelegate(*m_app_up, m_debugger));
7626
7627 MenuDelegateSP app_menu_delegate_sp =
7628 std::static_pointer_cast<MenuDelegate>(app_delegate_sp);
7629 MenuSP lldb_menu_sp(
7630 new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB));
7631 MenuSP exit_menuitem_sp(
7632 new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit));
7633 exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit);
7634 lldb_menu_sp->AddSubmenu(std::make_shared<Menu>(
7635 "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout));
7636 lldb_menu_sp->AddSubmenu(std::make_shared<Menu>(Menu::Type::Separator));
7637 lldb_menu_sp->AddSubmenu(exit_menuitem_sp);
7638
7639 MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2),
7640 ApplicationDelegate::eMenuID_Target));
7641 target_menu_sp->AddSubmenu(std::make_shared<Menu>(
7642 "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate));
7643 target_menu_sp->AddSubmenu(std::make_shared<Menu>(
7644 "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete));
7645
7646 MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3),
7647 ApplicationDelegate::eMenuID_Process));
7648 process_menu_sp->AddSubmenu(std::make_shared<Menu>(
7649 "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach));
7650 process_menu_sp->AddSubmenu(std::make_shared<Menu>(
7651 "Detach and resume", nullptr, 'd',
7652 ApplicationDelegate::eMenuID_ProcessDetachResume));
7653 process_menu_sp->AddSubmenu(std::make_shared<Menu>(
7654 "Detach suspended", nullptr, 's',
7655 ApplicationDelegate::eMenuID_ProcessDetachSuspended));
7656 process_menu_sp->AddSubmenu(std::make_shared<Menu>(
7657 "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch));
7658 process_menu_sp->AddSubmenu(std::make_shared<Menu>(Menu::Type::Separator));
7659 process_menu_sp->AddSubmenu(
7660 std::make_shared<Menu>("Continue", nullptr, 'c',
7661 ApplicationDelegate::eMenuID_ProcessContinue));
7662 process_menu_sp->AddSubmenu(std::make_shared<Menu>(
7663 "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt));
7664 process_menu_sp->AddSubmenu(std::make_shared<Menu>(
7665 "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill));
7666
7667 MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4),
7668 ApplicationDelegate::eMenuID_Thread));
7669 thread_menu_sp->AddSubmenu(std::make_shared<Menu>(
7670 "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn));
7671 thread_menu_sp->AddSubmenu(
7672 std::make_shared<Menu>("Step Over", nullptr, 'v',
7673 ApplicationDelegate::eMenuID_ThreadStepOver));
7674 thread_menu_sp->AddSubmenu(std::make_shared<Menu>(
7675 "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut));
7676
7677 MenuSP view_menu_sp(
7678 new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View));
7679 view_menu_sp->AddSubmenu(std::make_shared<Menu>(
7680 "Backtrace", nullptr, 't', ApplicationDelegate::eMenuID_ViewBacktrace));
7681 view_menu_sp->AddSubmenu(std::make_shared<Menu>(
7682 "Registers", nullptr, 'r', ApplicationDelegate::eMenuID_ViewRegisters));
7683 view_menu_sp->AddSubmenu(std::make_shared<Menu>(
7684 "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource));
7685 view_menu_sp->AddSubmenu(std::make_shared<Menu>(
7686 "Variables", nullptr, 'v', ApplicationDelegate::eMenuID_ViewVariables));
7687 view_menu_sp->AddSubmenu(
7688 std::make_shared<Menu>("Breakpoints", nullptr, 'b',
7689 ApplicationDelegate::eMenuID_ViewBreakpoints));
7690
7691 MenuSP help_menu_sp(
7692 new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help));
7693 help_menu_sp->AddSubmenu(std::make_shared<Menu>(
7694 "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp));
7695
7696 m_app_up->Initialize();
7697 WindowSP &main_window_sp = m_app_up->GetMainWindow();
7698
7699 MenuSP menubar_sp(new Menu(Menu::Type::Bar));
7700 menubar_sp->AddSubmenu(lldb_menu_sp);
7701 menubar_sp->AddSubmenu(target_menu_sp);
7702 menubar_sp->AddSubmenu(process_menu_sp);
7703 menubar_sp->AddSubmenu(thread_menu_sp);
7704 menubar_sp->AddSubmenu(view_menu_sp);
7705 menubar_sp->AddSubmenu(help_menu_sp);
7706 menubar_sp->SetDelegate(app_menu_delegate_sp);
7707
7708 Rect content_bounds = main_window_sp->GetFrame();
7709 Rect menubar_bounds = content_bounds.MakeMenuBar();
7710 Rect status_bounds = content_bounds.MakeStatusBar();
7711 Rect source_bounds;
7712 Rect variables_bounds;
7713 Rect threads_bounds;
7714 Rect source_variables_bounds;
7715 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
7716 threads_bounds);
7717 source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds,
7718 variables_bounds);
7719
7720 WindowSP menubar_window_sp =
7721 main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false);
7722 // Let the menubar get keys if the active window doesn't handle the keys
7723 // that are typed so it can respond to menubar key presses.
7724 menubar_window_sp->SetCanBeActive(
7725 false); // Don't let the menubar become the active window
7726 menubar_window_sp->SetDelegate(menubar_sp);
7727
7728 WindowSP source_window_sp(
7729 main_window_sp->CreateSubWindow("Source", source_bounds, true));
7730 WindowSP variables_window_sp(
7731 main_window_sp->CreateSubWindow("Variables", variables_bounds, false));
7732 WindowSP threads_window_sp(
7733 main_window_sp->CreateSubWindow("Threads", threads_bounds, false));
7734 WindowSP status_window_sp(
7735 main_window_sp->CreateSubWindow("Status", status_bounds, false));
7736 status_window_sp->SetCanBeActive(
7737 false); // Don't let the status bar become the active window
7738 main_window_sp->SetDelegate(
7739 std::static_pointer_cast<WindowDelegate>(app_delegate_sp));
7740 source_window_sp->SetDelegate(
7741 WindowDelegateSP(new SourceFileWindowDelegate(m_debugger)));
7742 variables_window_sp->SetDelegate(
7743 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
7744 TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger));
7745 threads_window_sp->SetDelegate(WindowDelegateSP(
7746 new TreeWindowDelegate(m_debugger, thread_delegate_sp)));
7747 status_window_sp->SetDelegate(
7748 WindowDelegateSP(new StatusBarWindowDelegate(m_debugger)));
7749
7750 // All colors with black background.
7751 init_pair(1, COLOR_BLACK, COLOR_BLACK);
7752 init_pair(2, COLOR_RED, COLOR_BLACK);
7753 init_pair(3, COLOR_GREEN, COLOR_BLACK);
7754 init_pair(4, COLOR_YELLOW, COLOR_BLACK);
7755 init_pair(5, COLOR_BLUE, COLOR_BLACK);
7756 init_pair(6, COLOR_MAGENTA, COLOR_BLACK);
7757 init_pair(7, COLOR_CYAN, COLOR_BLACK);
7758 init_pair(8, COLOR_WHITE, COLOR_BLACK);
7759 // All colors with blue background.
7760 init_pair(9, COLOR_BLACK, COLOR_BLUE);
7761 init_pair(10, COLOR_RED, COLOR_BLUE);
7762 init_pair(11, COLOR_GREEN, COLOR_BLUE);
7763 init_pair(12, COLOR_YELLOW, COLOR_BLUE);
7764 init_pair(13, COLOR_BLUE, COLOR_BLUE);
7765 init_pair(14, COLOR_MAGENTA, COLOR_BLUE);
7766 init_pair(15, COLOR_CYAN, COLOR_BLUE);
7767 init_pair(16, COLOR_WHITE, COLOR_BLUE);
7768 // These must match the order in the color indexes enum.
7769 init_pair(17, COLOR_BLACK, COLOR_WHITE);
7770 init_pair(18, COLOR_MAGENTA, COLOR_WHITE);
7771 static_assert(LastColorPairIndex == 18, "Color indexes do not match.");
7772
7773 define_key("\033[Z", KEY_SHIFT_TAB);
7774 define_key("\033\015", KEY_ALT_ENTER);
7775 }
7776}
7777
7778void IOHandlerCursesGUI::Deactivate() { m_app_up->Terminate(); }
7779
7781 m_app_up->Run(m_debugger);
7782 SetIsDone(true);
7783}
7784
7786
7788
7790 return m_debugger.GetCommandInterpreter().IOHandlerInterrupt(*this);
7791}
7792
7794
7796 m_app_up->TerminalSizeChanged();
7797}
7798
7799#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 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:301
bool SetLoadAddress(lldb::addr_t load_addr, Target *target, bool allow_section_end=false)
Set the address to represent load_addr.
Definition Address.cpp:1034
uint32_t CalculateSymbolContext(SymbolContext *sc, lldb::SymbolContextItem resolve_scope=lldb::eSymbolContextEverything) const
Reconstruct a symbol context from an address.
Definition Address.cpp:819
lldb::addr_t GetOpcodeLoadAddress(Target *target, AddressClass addr_class=AddressClass::eInvalid) const
Get the load address as an opcode load address.
Definition Address.cpp:358
@ 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:396
lldb::addr_t GetFileAddress() const
Get the file address.
Definition Address.cpp:281
bool IsValid() const
Check if the object state is valid.
Definition Address.h:355
Symbol * CalculateSymbolContextSymbol() const
Definition Address.cpp:887
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.
void Dump(Stream *s, const char *value_if_empty=nullptr) const
Dump the object description to a stream.
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:100
void CancelForwardEvents(const lldb::ListenerSP &listener_sp)
void EnableForwardEvents(const lldb::ListenerSP &listener_sp)
PlatformList & GetPlatformList()
Definition Debugger.h:222
std::pair< iterator, bool > insert(llvm::StringRef KeyEqValue)
Definition Environment.h:71
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
void SetDirectory(ConstString directory)
Directory string set accessor.
Definition FileSpec.cpp:342
const ConstString & GetFilename() const
Filename string const get accessor.
Definition FileSpec.h:250
const ConstString & GetDirectory() const
Directory string const get accessor.
Definition FileSpec.h:234
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:713
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(const char *name)
Definition Listener.cpp:372
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:321
static llvm::StringRef GetPlatformPluginNameAtIndex(uint32_t idx)
static llvm::StringRef GetProcessPluginNameAtIndex(uint32_t idx)
void SetContinueOnceAttached(bool b)
Definition Process.h:154
void SetProcessPluginName(llvm::StringRef plugin)
Definition Process.h:164
bool GetContinueOnceAttached() const
Definition Process.h:152
void SetExecutableFile(const FileSpec &exe_file, bool add_exe_file_as_first_arg)
void SetProcessID(lldb::pid_t pid)
Definition ProcessInfo.h:70
FileSpec & GetExecutableFile()
Definition ProcessInfo.h:43
Environment & GetEnvironment()
Definition ProcessInfo.h:88
ArchSpec & GetArchitecture()
Definition ProcessInfo.h:62
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:540
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:3792
ThreadList & GetThreadList()
Definition Process.h:2379
Status Resume()
Resumes all of a process's threads as configured using the Thread run control functions.
Definition Process.cpp:1332
int GetExitStatus()
Get the exit status for a process.
Definition Process.cpp:1028
Status Detach(bool keep_stopped)
Detaches from a running or stopped process.
Definition Process.cpp:3736
lldb::StateType GetState()
Get accessor for the current process state.
Definition Process.cpp:1278
const char * GetExitDescription()
Get a textual description of what the process exited.
Definition Process.cpp:1036
static llvm::StringRef GetStaticBroadcasterClass()
Definition Process.cpp:446
virtual bool IsAlive()
Check if a process is still alive.
Definition Process.cpp:1102
uint32_t GetStopID() const
Definition Process.h:1491
Status Halt(bool clear_thread_plans=false, bool use_run_lock=true)
Halts a running process.
Definition Process.cpp:3582
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:511
BreakpointList & GetBreakpointList(bool internal=false)
Definition Target.cpp:423
const lldb::ProcessSP & GetProcessSP() const
Definition Target.cpp:328
bool RemoveBreakpointByID(lldb::break_id_t break_id)
Definition Target.cpp:1175
Status Launch(ProcessLaunchInfo &launch_info, Stream *stream)
Definition Target.cpp:3553
lldb::ModuleSP GetExecutableModule()
Gets the module for the main executable.
Definition Target.cpp:1593
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:504
Status Attach(ProcessAttachInfo &attach_info, Stream *stream)
Definition Target.cpp:3756
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:434
uint32_t GetIndexID() const
Definition Thread.cpp:1449
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:2323
uint32_t GetSelectedFrameIndex(SelectMostRelevant select_most_relevant)
Definition Thread.h:470
lldb::ProcessSP GetProcess() const
Definition Thread.h:161
virtual Status StepOut(uint32_t frame_idx=0)
Default implementation for stepping out.
Definition Thread.cpp:2356
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:430
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:2287
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:1016
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:1010
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:82
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