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