7743 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8 9#include "lldb/Core/IOHandlerCursesGUI.h"10#include "lldb/Host/Config.h"11 12#if LLDB_ENABLE_CURSES13#if CURSES_HAVE_NCURSES_CURSES_H14#include <ncurses/curses.h>15#include <ncurses/panel.h>16#else17#include <curses.h>18#include <panel.h>19#endif20#endif21 22#if defined(__APPLE__)23#include <deque>24#endif25#include <memory>26#include <string>27 28#include "lldb/Core/Debugger.h"29#include "lldb/Host/File.h"30#include "lldb/Utility/AnsiTerminal.h"31#include "lldb/Utility/Predicate.h"32#include "lldb/Utility/Status.h"33#include "lldb/Utility/StreamString.h"34#include "lldb/Utility/StringList.h"35#include "lldb/ValueObject/ValueObjectUpdater.h"36#include "lldb/lldb-forward.h"37 38#include "lldb/Interpreter/CommandCompletions.h"39#include "lldb/Interpreter/CommandInterpreter.h"40#include "lldb/Interpreter/OptionGroupPlatform.h"41 42#if LLDB_ENABLE_CURSES43#include "lldb/Breakpoint/BreakpointLocation.h"44#include "lldb/Core/Module.h"45#include "lldb/Core/PluginManager.h"46#include "lldb/Symbol/Block.h"47#include "lldb/Symbol/CompileUnit.h"48#include "lldb/Symbol/Function.h"49#include "lldb/Symbol/Symbol.h"50#include "lldb/Symbol/VariableList.h"51#include "lldb/Target/Process.h"52#include "lldb/Target/RegisterContext.h"53#include "lldb/Target/StackFrame.h"54#include "lldb/Target/StopInfo.h"55#include "lldb/Target/Target.h"56#include "lldb/Target/Thread.h"57#include "lldb/Utility/State.h"58#include "lldb/ValueObject/ValueObject.h"59#include "lldb/ValueObject/ValueObjectRegister.h"60#endif61 62#include "llvm/ADT/StringRef.h"63 64#ifdef _WIN3265#include "lldb/Host/windows/windows.h"66#endif67 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, windows86#if LLDB_ENABLE_CURSES87 88#define KEY_CTRL_A 189#define KEY_CTRL_E 590#define KEY_CTRL_K 1191#define KEY_RETURN 1092#define KEY_ESCAPE 2793#define KEY_DELETE 12794 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 0113type summary add -s "x=${var.x}, y=${var.y}" curses::Point114type summary add -s "w=${var.width}, h=${var.height}" curses::Size115type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect116#endif117 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. This210 // 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 = 2275};276 277enum class MenuActionResult {278 Handled,279 NotHandled,280 Quit // Exit all menus and quit281};282 283struct KeyHelp {284 int ch;285 const char *description;286};287 288// COLOR_PAIR index names289enum {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 = MagentaOnWhite312};313 314class WindowDelegate {315public:316 virtual ~WindowDelegate() = default;317 318 virtual bool WindowDelegateDraw(Window &window, bool force) {319 return false; // Drawing not handled320 }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 surface351// have a width, a height, a cursor position, and a multitude of drawing352// operations. This type should be sub-classed to get an actually useful ncurses353// 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 else371 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's487 // how we get source lines from the Highligher class. Read the line and488 // convert color escape sequences to curses color attributes. Use489 // first_skip_count to skip leading visible characters. Returns false if all490 // 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);525 UNUSED_IF_ASSERT_DISABLED(consumed);526 // This is written to match our Highlighter classes, which seem to527 // generate only foreground color escape sequences. If necessary, this528 // will need to be extended.529 // Only 8 basic foreground colors, underline and reset, our Highlighter530 // 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_END540 << "' 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 window628 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-create652 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-create665 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_window685 ? ::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 else725 ::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 else752 ::touchwin(stdscr);753 }754 755 // Window drawing utilities756 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 else761 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 else838 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 first849 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. This864 // is typically only for a menubar. Make a copy of the subwindows in case865 // any HandleChar() functions muck with the subwindows. If we don't do866 // 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 active890 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 else920 return true; // Top level window is always active921 }922 923 void SelectNextWindowAsActive() {924 // Move active focus to next window925 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 window947 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// Forms989/////////990 991// A scroll context defines a vertical region that needs to be visible in a992// scrolling area. The region is defined by the index of the start and end lines993// of the region. The start and end lines may be equal, in which case, the994// 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 will1013 // 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. By1017 // default, the scroll context spans the whole field. Bigger fields with1018 // internal navigation should override this method to provide a finer context.1019 // Typical override methods would first get the scroll context of the internal1020 // 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 that1026 // is equal to the height returned by FieldDelegateGetHeight(). If the field1027 // 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 user1036 // navigates to the next or the previous field. This is particularly useful to1037 // do in-field validation and error setting. Fields with internal navigation1038 // should call this method on their fields.1039 virtual void FieldDelegateExitCallback() {}1040 1041 // Fields may have internal navigation, for instance, a List Field have1042 // multiple internal elements, which needs to be navigated. To allow for this1043 // 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 should1045 // only handle the navigation keys when the field contains a single element or1046 // have the last or first element selected depending on if the user is1047 // navigating forward or backward. Additionally, once a field is selected in1048 // the forward or backward direction, its first or last internal element1049 // 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 field1052 // 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 field1056 // 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 possible1089 // 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 for1097 // 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 actual1101 // 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 else1129 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 cursor1204 // 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 cursor1212 // 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 field1245 // content, false otherwise.1246 virtual bool IsAcceptableChar(int key) {1247 // The behavior of isprint is undefined when the value is not representable1248 // 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 range1330 // [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 [ ] Label1463 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 selected1525 // 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 visible1534 // 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 =1647 PluginManager::GetPlatformPluginNameAtIndex(i++);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 else1706 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 is1717 // selected, then m_selection_index signifies the particular field that1718 // 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 the1722 // same type. Each field has a Remove button next to it that removes the1723 // corresponding field. Finally, the last line contains a New button to add a1724 // 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 New1734 // 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 the1763 // 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 as1768 // 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 } else1855 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 key1964 // 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 be2014 // 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 the2058 // 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 between2066 // 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 is2128 // selected, its last element is selected, and it didn't handle the key, then2129 // 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 else2163 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 EnvironmentVariableFieldDelegate2214 : 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 EnvironmentVariableListFieldDelegate2232 : 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 is2318 // set and false is returned. This method is usually called at the start of an2319 // 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 else2476 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 is2481 // selected.2482 enum class SelectionType { Field, Action };2483 2484 // A form window is padded by one character from all sides. First, if an error2485 // message exists, it is drawn followed by a separator. Then one or more2486 // fields are drawn. Finally, all available actions are drawn on a single2487 // 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 as2542 // 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 invalid2557 // and needs to be set to the last line in the content. This can happen when2558 // 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 to2632 // the derived window starting at the first visible line. This essentially2633 // 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 always2806 // 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 key2834 // 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 some2842 // 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 Instances2869///////////////////////////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 else2939 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 else2949 m_plugin_field->FieldDelegateHide();2950 }2951 2952 // Get the basename of the target's main executable if available, empty string2953 // 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;3130 return eLoadDependentsDefault;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 else3327 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 =3353 target->GetProcessLaunchInfo().GetArguments();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 the3411 // 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 else3456 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 else3463 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 else3470 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();3484 launch_info.GetArchitecture() = Platform::GetAugmentedArchSpec(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// Searchers3647////////////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 text3661 // 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 executed3665 // 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 field3679 // is first drawn for inputting the searcher request, then a list of matches3680 // 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 match3695 // 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 Instances3821//////////////////////////////3822 3823// This is a searcher delegate wrapper around CommandCompletions common3824// callbacks. The callbacks are only given the match string. The completion_mask3825// 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);3844 lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(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 to3859 // the callback as a string.3860 CallbackType m_callback;3861 StringList m_matches;3862};3863 3864////////3865// Menus3866////////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 constructor3880 Menu(Type type);3881 3882 // Menuitem constructor3883 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 menu3922 // delegate, and if not, try our parent menu3923 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 constructor3972Menu::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 constructor3980Menu::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 menu4105 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 be4115 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 menu4143 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 with4186 // dynamic content and also in case check marks, and any other menu4187 // decorations need to be calculated4188 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 else4214 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 else4229 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 in4245 // case other chars are entered for escaped sequences4246 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 resort4295 // to polling every 0.5 seconds. We could poll for stdin ourselves and4296 // then pass the keys down but then we need to translate all of the escape4297 // sequences ourselves. So we resort to polling for input because we need4298 // to receive async process events while in this loop.4299 4300 halfdelay(delay_in_tenths_of_a_second); // Poll using some number of4301 // tenths of seconds seconds when4302 // 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#endif4313 4314 while (!done) {4315 if (m_update_screen) {4316 m_window_sp->Draw(false);4317 // All windows should be calling Window::DeferredRefresh() instead of4318 // Window::Refresh() so we can do a single update and avoid any screen4319 // blinking4320 update_panels();4321 4322 // Cursor hiding isn't working on MacOSX, so hide it in the top left4323 // corner4324 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 default4332 // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if4333 // possible4334 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#else4368 int ch = m_window_sp->GetChar();4369 4370#endif4371 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 events4376 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 view4389 }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 redraw4402 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 else4444 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 curses4484} // namespace lldb_private4485 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 the4547 // "symbol" UTF8 string below4548 // const char *symbol = "";4549 // if (row.expanded)4550 // symbol = "\xe2\x96\xbd ";4551 // else4552 // 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 // else4560 // 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 child4574 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, the4609 // 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, in4614 // 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 for4634 // the root item4635 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 == nullptr4644 ? 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 the4703 // number of children if the item is expanded4704 if (m_parent == nullptr || expanded)4705 GetNumChildren();4706 4707 for (auto &item : m_children) {4708 if (expanded)4709 item.CalculateRowIndexes(row_idx);4710 else4711 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 the4736 // "symbol" UTF8 string below4737 // const char *symbol = "";4738 // if (row.expanded)4739 // symbol = "\xe2\x96\xbd ";4740 // else4741 // symbol = "\xe2\x96\xb7 ";4742 // window.PutCString (symbol);4743 4744 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a4745 // 'v' or '>' character...4746 // if (expanded)4747 // window.PutChar (ACS_DARROW);4748 // else4749 // 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 are4775 // done drawing and can exit this for loop4776 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 yet4782 }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 child4791 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 of4883 // rows is less than the num visible rows, then make sure we show all the4884 // 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 visible4889 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 row4899 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);4900 4901 return true; // Drawing handled4902 }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 key4930 if (m_first_visible_row > 0) {4931 if (m_first_visible_row > m_max_y)4932 m_first_visible_row -= m_max_y;4933 else4934 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 key4945 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 pressed4996 if (m_selected_item) {4997 if (m_selected_item->IsExpanded())4998 m_selected_item->Unexpand();4999 else5000 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't5029// 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() {5048 FormatEntity::Parse(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 date5140 if (!m_frame_delegate_sp) {5141 // Always expand the thread item the first time we show it5142 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;5184 lldb::user_id_t m_tid = LLDB_INVALID_THREAD_ID;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 date5238 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 it5244 // 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,5333 Address::DumpStyleInvalid);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 else5380 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,5391 Address::DumpStyleModuleWithFileAddress);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 of5572 // rows is less than the num visible rows, then make sure we show all the5573 // 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 visible5578 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 row5586 m_selected_row = GetRowForRowIndex(m_selected_row_idx);5587 // Keep the cursor on the selected row so the highlight and the cursor are5588 // always on the same line5589 if (m_selected_row)5590 window.MoveCursor(m_selected_row->x, m_selected_row->y);5591 5592 return true; // Drawing handled5593 }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 item5643 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 names5652 g_options.show_types = !g_options.show_types;5653 return eKeyHandled;5654 5655 case ',':5656 case KEY_PPAGE:5657 // Page up key5658 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 else5662 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 key5670 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 pressed5706 if (m_selected_row) {5707 if (m_selected_row->expanded)5708 m_selected_row->Unexpand();5709 else5710 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 // > 0x25B75826 // \/ 0x25BD5827 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 structure5832 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 running5926 }5927 }5928 5929 ValueObjectList local_values;5930 if (frame_block) {5931 // Only update the variables if they have changed5932 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 else5946 local_values.Append(value_sp);5947 }5948 }5949 // Update the values5950 SetValues(local_values);5951 }5952 }5953 } else {5954 m_frame_block = nullptr;5955 // Update the values with an empty list if there is no frame5956 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 running6001 else {6002 // Update the values with an empty list if there is no process or the6003 // process isn't alive anymore6004 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_EVENT6195 case KEY_EVENT:6196 return "We were interrupted by an event";6197#endif6198 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 else6210 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 else6248 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 key6268 // press will cause us to exit6269 } 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 else6287 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_HelpGUIHelp6344 };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 happen6353 }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 =6438 thread->GetSelectedFrameIndex(SelectMostRelevantFrame);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 stopped6531 // when the Process menu gets selected and is about to display its6532 // 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 were6568 // previously added6569 submenus.erase(submenus.begin() + 7, submenus.end());6570 }6571 // Since we are adding and removing items we need to recalculate the6572 // name lengths6573 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 the6591 // registers window6592 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 back6597 // to the source view6598 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 registers6606 // window into two columns where the left hand side will be the6607 // variables and the right hand side will be the registers6608 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 window6615 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 the6641 // variables window6642 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 back6647 // to the source view6648 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 the6657 // left hand side will be the variables and the right hand side will6658 // be the registers6659 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 window6666 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 area6688 // it used to occupy to the threads window. If it doesn't exist, split6689 // the threads window horizontally into two windows where the top window6690 // is the threads window and the bottom window is a newly added6691 // 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(),6763 frame->GetFrameCodeAddress().GetOpcodeLoadAddress(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 else6771 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 to6834 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 or6902 // 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 else6910 m_first_visible_line = 0;6911 }6912 } else {6913 // File changed, set selected line to the line with the PC6914 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 else6927 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 disassembly6936 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 source6983 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 passing7013 // argument to OutputColoredStringTruncated()), then if the selected7014 // 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 diamond7033 if (is_pc_line)7034 window.PutChar(ACS_DIAMOND);7035 else7036 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 much7056 // 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 disassembly7095 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 selected7151 // line isn't the same as the PC line, highlight it differently7152 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 diamond7175 if (is_pc_line)7176 window.PutChar(ACS_DIAMOND);7177 else7178 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 handled7237 }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 key7266 if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines)7267 m_first_visible_line -= num_visible_lines;7268 else7269 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 key7276 {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 else7282 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 breakpoint7316 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 modules7322 m_file_sp->GetSupportFile()->GetSpecOnly(), // Source file7323 m_selected_line +7324 1, // Source line number (m_selected_line is zero based)7325 0, // Unspecified column.7326 0, // No offset7327 eLazyBoolCalculate, // Check inlines using global setting7328 eLazyBoolCalculate, // Skip prologue using global setting,7329 false, // internal7330 false, // request_hardware7331 eLazyBoolCalculate); // move_to_nearest_code7332 // Make breakpoint one shot7333 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::Address7346 false, // internal7347 false); // request_hardware7348 // Make breakpoint one shot7349 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 line7356 ToggleBreakpointOnSelectedLine();7357 return eKeyHandled;7358 7359 case 'D': // 'D' == detach and keep stopped7360 {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' == continue7370 {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 =7387 thread->GetSelectedFrameIndex(SelectMostRelevantFrame);7388 exe_ctx.GetThreadRef().StepOut(frame_idx);7389 }7390 }7391 return eKeyHandled;7392 7393 case 'n': // 'n' == step over7394 case 'N': // 'N' == step over instruction7395 {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 into7407 case 'S': // 'S' == step into instruction7408 {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 up7420 case 'd': // 'd' == frame down7421 {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 =7427 thread->GetSelectedFrameIndex(SelectMostRelevantFrame);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))7435 exe_ctx.SetFrameSP(thread->GetSelectedFrame(SelectMostRelevantFrame));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);7474 UNUSED_IF_ASSERT_DISABLED(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 modules7483 m_file_sp->GetSupportFile()->GetSpecOnly(), // Source file7484 m_selected_line +7485 1, // Source line number (m_selected_line is zero based)7486 0, // No column specified.7487 0, // No offset7488 eLazyBoolCalculate, // Check inlines using global setting7489 eLazyBoolCalculate, // Skip prologue using global setting,7490 false, // internal7491 false, // request_hardware7492 eLazyBoolCalculate); // move_to_nearest_code7493 } 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);7517 UNUSED_IF_ASSERT_DISABLED(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::Address7525 false, // internal7526 false); // request_hardware7527 }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;7540 lldb::user_id_t m_tid = LLDB_INVALID_THREAD_ID;7541 int m_line_width = 4;7542 uint32_t m_selected_line = 0; // The selected line7543 uint32_t m_pc_line = 0; // The line with the PC7544 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 7556IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger)7557 : IOHandler(debugger, IOHandler::Type::Curses) {}7558 7559void IOHandlerCursesGUI::Activate() {7560 IOHandler::Activate();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 delegate7567 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 keys7666 // 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 window7669 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 window7681 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 7723void IOHandlerCursesGUI::Run() {7724 m_app_up->Run(m_debugger);7725 SetIsDone(true);7726}7727 7728IOHandlerCursesGUI::~IOHandlerCursesGUI() = default;7729 7730void IOHandlerCursesGUI::Cancel() {}7731 7732bool IOHandlerCursesGUI::Interrupt() {7733 return m_debugger.GetCommandInterpreter().IOHandlerInterrupt(*this);7734}7735 7736void IOHandlerCursesGUI::GotEOF() {}7737 7738void IOHandlerCursesGUI::TerminalSizeChanged() {7739 m_app_up->TerminalSizeChanged();7740}7741 7742#endif // LLDB_ENABLE_CURSES7743