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