Path: blob/main/contrib/llvm-project/lldb/source/Core/IOHandlerCursesGUI.cpp
39587 views
//===-- IOHandlerCursesGUI.cpp --------------------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "lldb/Core/IOHandlerCursesGUI.h"9#include "lldb/Host/Config.h"1011#if LLDB_ENABLE_CURSES12#if CURSES_HAVE_NCURSES_CURSES_H13#include <ncurses/curses.h>14#include <ncurses/panel.h>15#else16#include <curses.h>17#include <panel.h>18#endif19#endif2021#if defined(__APPLE__)22#include <deque>23#endif24#include <string>2526#include "lldb/Core/Debugger.h"27#include "lldb/Core/ValueObjectUpdater.h"28#include "lldb/Host/File.h"29#include "lldb/Utility/AnsiTerminal.h"30#include "lldb/Utility/Predicate.h"31#include "lldb/Utility/Status.h"32#include "lldb/Utility/StreamString.h"33#include "lldb/Utility/StringList.h"34#include "lldb/lldb-forward.h"3536#include "lldb/Interpreter/CommandCompletions.h"37#include "lldb/Interpreter/CommandInterpreter.h"38#include "lldb/Interpreter/OptionGroupPlatform.h"3940#if LLDB_ENABLE_CURSES41#include "lldb/Breakpoint/BreakpointLocation.h"42#include "lldb/Core/Module.h"43#include "lldb/Core/PluginManager.h"44#include "lldb/Core/ValueObject.h"45#include "lldb/Core/ValueObjectRegister.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#endif5960#include "llvm/ADT/StringRef.h"6162#ifdef _WIN3263#include "lldb/Host/windows/windows.h"64#endif6566#include <memory>67#include <mutex>6869#include <cassert>70#include <cctype>71#include <cerrno>72#include <cstdint>73#include <cstdio>74#include <cstring>75#include <functional>76#include <optional>77#include <type_traits>7879using namespace lldb;80using namespace lldb_private;81using llvm::StringRef;8283// we may want curses to be disabled for some builds for instance, windows84#if LLDB_ENABLE_CURSES8586#define KEY_CTRL_A 187#define KEY_CTRL_E 588#define KEY_CTRL_K 1189#define KEY_RETURN 1090#define KEY_ESCAPE 2791#define KEY_DELETE 1279293#define KEY_SHIFT_TAB (KEY_MAX + 1)94#define KEY_ALT_ENTER (KEY_MAX + 2)9596namespace curses {97class Menu;98class MenuDelegate;99class Window;100class WindowDelegate;101typedef std::shared_ptr<Menu> MenuSP;102typedef std::shared_ptr<MenuDelegate> MenuDelegateSP;103typedef std::shared_ptr<Window> WindowSP;104typedef std::shared_ptr<WindowDelegate> WindowDelegateSP;105typedef std::vector<MenuSP> Menus;106typedef std::vector<WindowSP> Windows;107typedef std::vector<WindowDelegateSP> WindowDelegates;108109#if 0110type summary add -s "x=${var.x}, y=${var.y}" curses::Point111type summary add -s "w=${var.width}, h=${var.height}" curses::Size112type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect113#endif114115struct Point {116int x;117int y;118119Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}120121void Clear() {122x = 0;123y = 0;124}125126Point &operator+=(const Point &rhs) {127x += rhs.x;128y += rhs.y;129return *this;130}131132void Dump() { printf("(x=%i, y=%i)\n", x, y); }133};134135bool operator==(const Point &lhs, const Point &rhs) {136return lhs.x == rhs.x && lhs.y == rhs.y;137}138139bool operator!=(const Point &lhs, const Point &rhs) {140return lhs.x != rhs.x || lhs.y != rhs.y;141}142143struct Size {144int width;145int height;146Size(int w = 0, int h = 0) : width(w), height(h) {}147148void Clear() {149width = 0;150height = 0;151}152153void Dump() { printf("(w=%i, h=%i)\n", width, height); }154};155156bool operator==(const Size &lhs, const Size &rhs) {157return lhs.width == rhs.width && lhs.height == rhs.height;158}159160bool operator!=(const Size &lhs, const Size &rhs) {161return lhs.width != rhs.width || lhs.height != rhs.height;162}163164struct Rect {165Point origin;166Size size;167168Rect() : origin(), size() {}169170Rect(const Point &p, const Size &s) : origin(p), size(s) {}171172void Clear() {173origin.Clear();174size.Clear();175}176177void Dump() {178printf("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width,179size.height);180}181182void Inset(int w, int h) {183if (size.width > w * 2)184size.width -= w * 2;185origin.x += w;186187if (size.height > h * 2)188size.height -= h * 2;189origin.y += h;190}191192// Return a status bar rectangle which is the last line of this rectangle.193// This rectangle will be modified to not include the status bar area.194Rect MakeStatusBar() {195Rect status_bar;196if (size.height > 1) {197status_bar.origin.x = origin.x;198status_bar.origin.y = size.height;199status_bar.size.width = size.width;200status_bar.size.height = 1;201--size.height;202}203return status_bar;204}205206// Return a menubar rectangle which is the first line of this rectangle. This207// rectangle will be modified to not include the menubar area.208Rect MakeMenuBar() {209Rect menubar;210if (size.height > 1) {211menubar.origin.x = origin.x;212menubar.origin.y = origin.y;213menubar.size.width = size.width;214menubar.size.height = 1;215++origin.y;216--size.height;217}218return menubar;219}220221void HorizontalSplitPercentage(float top_percentage, Rect &top,222Rect &bottom) const {223float top_height = top_percentage * size.height;224HorizontalSplit(top_height, top, bottom);225}226227void HorizontalSplit(int top_height, Rect &top, Rect &bottom) const {228top = *this;229if (top_height < size.height) {230top.size.height = top_height;231bottom.origin.x = origin.x;232bottom.origin.y = origin.y + top.size.height;233bottom.size.width = size.width;234bottom.size.height = size.height - top.size.height;235} else {236bottom.Clear();237}238}239240void VerticalSplitPercentage(float left_percentage, Rect &left,241Rect &right) const {242float left_width = left_percentage * size.width;243VerticalSplit(left_width, left, right);244}245246void VerticalSplit(int left_width, Rect &left, Rect &right) const {247left = *this;248if (left_width < size.width) {249left.size.width = left_width;250right.origin.x = origin.x + left.size.width;251right.origin.y = origin.y;252right.size.width = size.width - left.size.width;253right.size.height = size.height;254} else {255right.Clear();256}257}258};259260bool operator==(const Rect &lhs, const Rect &rhs) {261return lhs.origin == rhs.origin && lhs.size == rhs.size;262}263264bool operator!=(const Rect &lhs, const Rect &rhs) {265return lhs.origin != rhs.origin || lhs.size != rhs.size;266}267268enum HandleCharResult {269eKeyNotHandled = 0,270eKeyHandled = 1,271eQuitApplication = 2272};273274enum class MenuActionResult {275Handled,276NotHandled,277Quit // Exit all menus and quit278};279280struct KeyHelp {281int ch;282const char *description;283};284285// COLOR_PAIR index names286enum {287// First 16 colors are 8 black background and 8 blue background colors,288// needed by OutputColoredStringTruncated().289BlackOnBlack = 1,290RedOnBlack,291GreenOnBlack,292YellowOnBlack,293BlueOnBlack,294MagentaOnBlack,295CyanOnBlack,296WhiteOnBlack,297BlackOnBlue,298RedOnBlue,299GreenOnBlue,300YellowOnBlue,301BlueOnBlue,302MagentaOnBlue,303CyanOnBlue,304WhiteOnBlue,305// Other colors, as needed.306BlackOnWhite,307MagentaOnWhite,308LastColorPairIndex = MagentaOnWhite309};310311class WindowDelegate {312public:313virtual ~WindowDelegate() = default;314315virtual bool WindowDelegateDraw(Window &window, bool force) {316return false; // Drawing not handled317}318319virtual HandleCharResult WindowDelegateHandleChar(Window &window, int key) {320return eKeyNotHandled;321}322323virtual const char *WindowDelegateGetHelpText() { return nullptr; }324325virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; }326};327328class HelpDialogDelegate : public WindowDelegate {329public:330HelpDialogDelegate(const char *text, KeyHelp *key_help_array);331332~HelpDialogDelegate() override;333334bool WindowDelegateDraw(Window &window, bool force) override;335336HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;337338size_t GetNumLines() const { return m_text.GetSize(); }339340size_t GetMaxLineLength() const { return m_text.GetMaxStringLength(); }341342protected:343StringList m_text;344int m_first_visible_line = 0;345};346347// A surface is an abstraction for something than can be drawn on. The surface348// have a width, a height, a cursor position, and a multitude of drawing349// operations. This type should be sub-classed to get an actually useful ncurses350// object, such as a Window or a Pad.351class Surface {352public:353enum class Type { Window, Pad };354355Surface(Surface::Type type) : m_type(type) {}356357WINDOW *get() { return m_window; }358359operator WINDOW *() { return m_window; }360361Surface SubSurface(Rect bounds) {362Surface subSurface(m_type);363if (m_type == Type::Pad)364subSurface.m_window =365::subpad(m_window, bounds.size.height, bounds.size.width,366bounds.origin.y, bounds.origin.x);367else368subSurface.m_window =369::derwin(m_window, bounds.size.height, bounds.size.width,370bounds.origin.y, bounds.origin.x);371return subSurface;372}373374// Copy a region of the surface to another surface.375void CopyToSurface(Surface &target, Point source_origin, Point target_origin,376Size size) {377::copywin(m_window, target.get(), source_origin.y, source_origin.x,378target_origin.y, target_origin.x,379target_origin.y + size.height - 1,380target_origin.x + size.width - 1, false);381}382383int GetCursorX() const { return getcurx(m_window); }384int GetCursorY() const { return getcury(m_window); }385void MoveCursor(int x, int y) { ::wmove(m_window, y, x); }386387void AttributeOn(attr_t attr) { ::wattron(m_window, attr); }388void AttributeOff(attr_t attr) { ::wattroff(m_window, attr); }389390int GetMaxX() const { return getmaxx(m_window); }391int GetMaxY() const { return getmaxy(m_window); }392int GetWidth() const { return GetMaxX(); }393int GetHeight() const { return GetMaxY(); }394Size GetSize() const { return Size(GetWidth(), GetHeight()); }395// Get a zero origin rectangle width the surface size.396Rect GetFrame() const { return Rect(Point(), GetSize()); }397398void Clear() { ::wclear(m_window); }399void Erase() { ::werase(m_window); }400401void SetBackground(int color_pair_idx) {402::wbkgd(m_window, COLOR_PAIR(color_pair_idx));403}404405void PutChar(int ch) { ::waddch(m_window, ch); }406void PutCString(const char *s, int len = -1) { ::waddnstr(m_window, s, len); }407408void PutCStringTruncated(int right_pad, const char *s, int len = -1) {409int bytes_left = GetWidth() - GetCursorX();410if (bytes_left > right_pad) {411bytes_left -= right_pad;412::waddnstr(m_window, s, len < 0 ? bytes_left : std::min(bytes_left, len));413}414}415416void Printf(const char *format, ...) __attribute__((format(printf, 2, 3))) {417va_list args;418va_start(args, format);419vw_printw(m_window, format, args);420va_end(args);421}422423void PrintfTruncated(int right_pad, const char *format, ...)424__attribute__((format(printf, 3, 4))) {425va_list args;426va_start(args, format);427StreamString strm;428strm.PrintfVarArg(format, args);429va_end(args);430PutCStringTruncated(right_pad, strm.GetData());431}432433void VerticalLine(int n, chtype v_char = ACS_VLINE) {434::wvline(m_window, v_char, n);435}436void HorizontalLine(int n, chtype h_char = ACS_HLINE) {437::whline(m_window, h_char, n);438}439void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {440::box(m_window, v_char, h_char);441}442443void TitledBox(const char *title, chtype v_char = ACS_VLINE,444chtype h_char = ACS_HLINE) {445Box(v_char, h_char);446int title_offset = 2;447MoveCursor(title_offset, 0);448PutChar('[');449PutCString(title, GetWidth() - title_offset);450PutChar(']');451}452453void Box(const Rect &bounds, chtype v_char = ACS_VLINE,454chtype h_char = ACS_HLINE) {455MoveCursor(bounds.origin.x, bounds.origin.y);456VerticalLine(bounds.size.height);457HorizontalLine(bounds.size.width);458PutChar(ACS_ULCORNER);459460MoveCursor(bounds.origin.x + bounds.size.width - 1, bounds.origin.y);461VerticalLine(bounds.size.height);462PutChar(ACS_URCORNER);463464MoveCursor(bounds.origin.x, bounds.origin.y + bounds.size.height - 1);465HorizontalLine(bounds.size.width);466PutChar(ACS_LLCORNER);467468MoveCursor(bounds.origin.x + bounds.size.width - 1,469bounds.origin.y + bounds.size.height - 1);470PutChar(ACS_LRCORNER);471}472473void TitledBox(const Rect &bounds, const char *title,474chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {475Box(bounds, v_char, h_char);476int title_offset = 2;477MoveCursor(bounds.origin.x + title_offset, bounds.origin.y);478PutChar('[');479PutCString(title, bounds.size.width - title_offset);480PutChar(']');481}482483// Curses doesn't allow direct output of color escape sequences, but that's484// how we get source lines from the Highligher class. Read the line and485// convert color escape sequences to curses color attributes. Use486// first_skip_count to skip leading visible characters. Returns false if all487// visible characters were skipped due to first_skip_count.488bool OutputColoredStringTruncated(int right_pad, StringRef string,489size_t skip_first_count,490bool use_blue_background) {491attr_t saved_attr;492short saved_pair;493bool result = false;494wattr_get(m_window, &saved_attr, &saved_pair, nullptr);495if (use_blue_background)496::wattron(m_window, COLOR_PAIR(WhiteOnBlue));497while (!string.empty()) {498size_t esc_pos = string.find(ANSI_ESC_START);499if (esc_pos == StringRef::npos) {500string = string.substr(skip_first_count);501if (!string.empty()) {502PutCStringTruncated(right_pad, string.data(), string.size());503result = true;504}505break;506}507if (esc_pos > 0) {508if (skip_first_count > 0) {509int skip = std::min(esc_pos, skip_first_count);510string = string.substr(skip);511skip_first_count -= skip;512esc_pos -= skip;513}514if (esc_pos > 0) {515PutCStringTruncated(right_pad, string.data(), esc_pos);516result = true;517string = string.drop_front(esc_pos);518}519}520bool consumed = string.consume_front(ANSI_ESC_START);521assert(consumed);522UNUSED_IF_ASSERT_DISABLED(consumed);523// This is written to match our Highlighter classes, which seem to524// generate only foreground color escape sequences. If necessary, this525// will need to be extended.526// Only 8 basic foreground colors, underline and reset, our Highlighter527// doesn't use anything else.528int value;529if (!!string.consumeInteger(10, value) || // Returns false on success.530!(value == 0 || value == ANSI_CTRL_UNDERLINE ||531(value >= ANSI_FG_COLOR_BLACK && value <= ANSI_FG_COLOR_WHITE))) {532llvm::errs() << "No valid color code in color escape sequence.\n";533continue;534}535if (!string.consume_front(ANSI_ESC_END)) {536llvm::errs() << "Missing '" << ANSI_ESC_END537<< "' in color escape sequence.\n";538continue;539}540if (value == 0) { // Reset.541wattr_set(m_window, saved_attr, saved_pair, nullptr);542if (use_blue_background)543::wattron(m_window, COLOR_PAIR(WhiteOnBlue));544} else if (value == ANSI_CTRL_UNDERLINE) {545::wattron(m_window, A_UNDERLINE);546} else {547// Mapped directly to first 16 color pairs (black/blue background).548::wattron(m_window, COLOR_PAIR(value - ANSI_FG_COLOR_BLACK + 1 +549(use_blue_background ? 8 : 0)));550}551}552wattr_set(m_window, saved_attr, saved_pair, nullptr);553return result;554}555556protected:557Type m_type;558WINDOW *m_window = nullptr;559};560561class Pad : public Surface {562public:563Pad(Size size) : Surface(Surface::Type::Pad) {564m_window = ::newpad(size.height, size.width);565}566567~Pad() { ::delwin(m_window); }568};569570class Window : public Surface {571public:572Window(const char *name)573: Surface(Surface::Type::Window), m_name(name), m_panel(nullptr),574m_parent(nullptr), m_subwindows(), m_delegate_sp(),575m_curr_active_window_idx(UINT32_MAX),576m_prev_active_window_idx(UINT32_MAX), m_delete(false),577m_needs_update(true), m_can_activate(true), m_is_subwin(false) {}578579Window(const char *name, WINDOW *w, bool del = true)580: Surface(Surface::Type::Window), m_name(name), m_panel(nullptr),581m_parent(nullptr), m_subwindows(), m_delegate_sp(),582m_curr_active_window_idx(UINT32_MAX),583m_prev_active_window_idx(UINT32_MAX), m_delete(del),584m_needs_update(true), m_can_activate(true), m_is_subwin(false) {585if (w)586Reset(w);587}588589Window(const char *name, const Rect &bounds)590: Surface(Surface::Type::Window), m_name(name), m_panel(nullptr),591m_parent(nullptr), m_subwindows(), m_delegate_sp(),592m_curr_active_window_idx(UINT32_MAX),593m_prev_active_window_idx(UINT32_MAX), m_delete(false),594m_needs_update(true), m_can_activate(true), m_is_subwin(false) {595Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y,596bounds.origin.y));597}598599virtual ~Window() {600RemoveSubWindows();601Reset();602}603604void Reset(WINDOW *w = nullptr, bool del = true) {605if (m_window == w)606return;607608if (m_panel) {609::del_panel(m_panel);610m_panel = nullptr;611}612if (m_window && m_delete) {613::delwin(m_window);614m_window = nullptr;615m_delete = false;616}617if (w) {618m_window = w;619m_panel = ::new_panel(m_window);620m_delete = del;621}622}623624// Get the rectangle in our parent window625Rect GetBounds() const { return Rect(GetParentOrigin(), GetSize()); }626627Rect GetCenteredRect(int width, int height) {628Size size = GetSize();629width = std::min(size.width, width);630height = std::min(size.height, height);631int x = (size.width - width) / 2;632int y = (size.height - height) / 2;633return Rect(Point(x, y), Size(width, height));634}635636int GetChar() { return ::wgetch(m_window); }637Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); }638int GetParentX() const { return getparx(m_window); }639int GetParentY() const { return getpary(m_window); }640void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); }641void Resize(int w, int h) { ::wresize(m_window, h, w); }642void Resize(const Size &size) {643::wresize(m_window, size.height, size.width);644}645void MoveWindow(const Point &origin) {646const bool moving_window = origin != GetParentOrigin();647if (m_is_subwin && moving_window) {648// Can't move subwindows, must delete and re-create649Size size = GetSize();650Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y,651origin.x),652true);653} else {654::mvwin(m_window, origin.y, origin.x);655}656}657658void SetBounds(const Rect &bounds) {659const bool moving_window = bounds.origin != GetParentOrigin();660if (m_is_subwin && moving_window) {661// Can't move subwindows, must delete and re-create662Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width,663bounds.origin.y, bounds.origin.x),664true);665} else {666if (moving_window)667MoveWindow(bounds.origin);668Resize(bounds.size);669}670}671672void Touch() {673::touchwin(m_window);674if (m_parent)675m_parent->Touch();676}677678WindowSP CreateSubWindow(const char *name, const Rect &bounds,679bool make_active) {680auto get_window = [this, &bounds]() {681return m_window682? ::subwin(m_window, bounds.size.height, bounds.size.width,683bounds.origin.y, bounds.origin.x)684: ::newwin(bounds.size.height, bounds.size.width,685bounds.origin.y, bounds.origin.x);686};687WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true);688subwindow_sp->m_is_subwin = subwindow_sp.operator bool();689subwindow_sp->m_parent = this;690if (make_active) {691m_prev_active_window_idx = m_curr_active_window_idx;692m_curr_active_window_idx = m_subwindows.size();693}694m_subwindows.push_back(subwindow_sp);695::top_panel(subwindow_sp->m_panel);696m_needs_update = true;697return subwindow_sp;698}699700bool RemoveSubWindow(Window *window) {701Windows::iterator pos, end = m_subwindows.end();702size_t i = 0;703for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {704if ((*pos).get() == window) {705if (m_prev_active_window_idx == i)706m_prev_active_window_idx = UINT32_MAX;707else if (m_prev_active_window_idx != UINT32_MAX &&708m_prev_active_window_idx > i)709--m_prev_active_window_idx;710711if (m_curr_active_window_idx == i)712m_curr_active_window_idx = UINT32_MAX;713else if (m_curr_active_window_idx != UINT32_MAX &&714m_curr_active_window_idx > i)715--m_curr_active_window_idx;716window->Erase();717m_subwindows.erase(pos);718m_needs_update = true;719if (m_parent)720m_parent->Touch();721else722::touchwin(stdscr);723return true;724}725}726return false;727}728729WindowSP FindSubWindow(const char *name) {730Windows::iterator pos, end = m_subwindows.end();731size_t i = 0;732for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {733if ((*pos)->m_name == name)734return *pos;735}736return WindowSP();737}738739void RemoveSubWindows() {740m_curr_active_window_idx = UINT32_MAX;741m_prev_active_window_idx = UINT32_MAX;742for (Windows::iterator pos = m_subwindows.begin();743pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) {744(*pos)->Erase();745}746if (m_parent)747m_parent->Touch();748else749::touchwin(stdscr);750}751752// Window drawing utilities753void DrawTitleBox(const char *title, const char *bottom_message = nullptr) {754attr_t attr = 0;755if (IsActive())756attr = A_BOLD | COLOR_PAIR(BlackOnWhite);757else758attr = 0;759if (attr)760AttributeOn(attr);761762Box();763MoveCursor(3, 0);764765if (title && title[0]) {766PutChar('<');767PutCString(title);768PutChar('>');769}770771if (bottom_message && bottom_message[0]) {772int bottom_message_length = strlen(bottom_message);773int x = GetWidth() - 3 - (bottom_message_length + 2);774775if (x > 0) {776MoveCursor(x, GetHeight() - 1);777PutChar('[');778PutCString(bottom_message);779PutChar(']');780} else {781MoveCursor(1, GetHeight() - 1);782PutChar('[');783PutCStringTruncated(1, bottom_message);784}785}786if (attr)787AttributeOff(attr);788}789790virtual void Draw(bool force) {791if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force))792return;793794for (auto &subwindow_sp : m_subwindows)795subwindow_sp->Draw(force);796}797798bool CreateHelpSubwindow() {799if (m_delegate_sp) {800const char *text = m_delegate_sp->WindowDelegateGetHelpText();801KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp();802if ((text && text[0]) || key_help) {803std::unique_ptr<HelpDialogDelegate> help_delegate_up(804new HelpDialogDelegate(text, key_help));805const size_t num_lines = help_delegate_up->GetNumLines();806const size_t max_length = help_delegate_up->GetMaxLineLength();807Rect bounds = GetBounds();808bounds.Inset(1, 1);809if (max_length + 4 < static_cast<size_t>(bounds.size.width)) {810bounds.origin.x += (bounds.size.width - max_length + 4) / 2;811bounds.size.width = max_length + 4;812} else {813if (bounds.size.width > 100) {814const int inset_w = bounds.size.width / 4;815bounds.origin.x += inset_w;816bounds.size.width -= 2 * inset_w;817}818}819820if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) {821bounds.origin.y += (bounds.size.height - num_lines + 2) / 2;822bounds.size.height = num_lines + 2;823} else {824if (bounds.size.height > 100) {825const int inset_h = bounds.size.height / 4;826bounds.origin.y += inset_h;827bounds.size.height -= 2 * inset_h;828}829}830WindowSP help_window_sp;831Window *parent_window = GetParent();832if (parent_window)833help_window_sp = parent_window->CreateSubWindow("Help", bounds, true);834else835help_window_sp = CreateSubWindow("Help", bounds, true);836help_window_sp->SetDelegate(837WindowDelegateSP(help_delegate_up.release()));838return true;839}840}841return false;842}843844virtual HandleCharResult HandleChar(int key) {845// Always check the active window first846HandleCharResult result = eKeyNotHandled;847WindowSP active_window_sp = GetActiveWindow();848if (active_window_sp) {849result = active_window_sp->HandleChar(key);850if (result != eKeyNotHandled)851return result;852}853854if (m_delegate_sp) {855result = m_delegate_sp->WindowDelegateHandleChar(*this, key);856if (result != eKeyNotHandled)857return result;858}859860// Then check for any windows that want any keys that weren't handled. This861// is typically only for a menubar. Make a copy of the subwindows in case862// any HandleChar() functions muck with the subwindows. If we don't do863// this, we can crash when iterating over the subwindows.864Windows subwindows(m_subwindows);865for (auto subwindow_sp : subwindows) {866if (!subwindow_sp->m_can_activate) {867HandleCharResult result = subwindow_sp->HandleChar(key);868if (result != eKeyNotHandled)869return result;870}871}872873return eKeyNotHandled;874}875876WindowSP GetActiveWindow() {877if (!m_subwindows.empty()) {878if (m_curr_active_window_idx >= m_subwindows.size()) {879if (m_prev_active_window_idx < m_subwindows.size()) {880m_curr_active_window_idx = m_prev_active_window_idx;881m_prev_active_window_idx = UINT32_MAX;882} else if (IsActive()) {883m_prev_active_window_idx = UINT32_MAX;884m_curr_active_window_idx = UINT32_MAX;885886// Find first window that wants to be active if this window is active887const size_t num_subwindows = m_subwindows.size();888for (size_t i = 0; i < num_subwindows; ++i) {889if (m_subwindows[i]->GetCanBeActive()) {890m_curr_active_window_idx = i;891break;892}893}894}895}896897if (m_curr_active_window_idx < m_subwindows.size())898return m_subwindows[m_curr_active_window_idx];899}900return WindowSP();901}902903bool GetCanBeActive() const { return m_can_activate; }904905void SetCanBeActive(bool b) { m_can_activate = b; }906907void SetDelegate(const WindowDelegateSP &delegate_sp) {908m_delegate_sp = delegate_sp;909}910911Window *GetParent() const { return m_parent; }912913bool IsActive() const {914if (m_parent)915return m_parent->GetActiveWindow().get() == this;916else917return true; // Top level window is always active918}919920void SelectNextWindowAsActive() {921// Move active focus to next window922const int num_subwindows = m_subwindows.size();923int start_idx = 0;924if (m_curr_active_window_idx != UINT32_MAX) {925m_prev_active_window_idx = m_curr_active_window_idx;926start_idx = m_curr_active_window_idx + 1;927}928for (int idx = start_idx; idx < num_subwindows; ++idx) {929if (m_subwindows[idx]->GetCanBeActive()) {930m_curr_active_window_idx = idx;931return;932}933}934for (int idx = 0; idx < start_idx; ++idx) {935if (m_subwindows[idx]->GetCanBeActive()) {936m_curr_active_window_idx = idx;937break;938}939}940}941942void SelectPreviousWindowAsActive() {943// Move active focus to previous window944const int num_subwindows = m_subwindows.size();945int start_idx = num_subwindows - 1;946if (m_curr_active_window_idx != UINT32_MAX) {947m_prev_active_window_idx = m_curr_active_window_idx;948start_idx = m_curr_active_window_idx - 1;949}950for (int idx = start_idx; idx >= 0; --idx) {951if (m_subwindows[idx]->GetCanBeActive()) {952m_curr_active_window_idx = idx;953return;954}955}956for (int idx = num_subwindows - 1; idx > start_idx; --idx) {957if (m_subwindows[idx]->GetCanBeActive()) {958m_curr_active_window_idx = idx;959break;960}961}962}963964const char *GetName() const { return m_name.c_str(); }965966protected:967std::string m_name;968PANEL *m_panel;969Window *m_parent;970Windows m_subwindows;971WindowDelegateSP m_delegate_sp;972uint32_t m_curr_active_window_idx;973uint32_t m_prev_active_window_idx;974bool m_delete;975bool m_needs_update;976bool m_can_activate;977bool m_is_subwin;978979private:980Window(const Window &) = delete;981const Window &operator=(const Window &) = delete;982};983984/////////985// Forms986/////////987988// A scroll context defines a vertical region that needs to be visible in a989// scrolling area. The region is defined by the index of the start and end lines990// of the region. The start and end lines may be equal, in which case, the991// region is a single line.992struct ScrollContext {993int start;994int end;995996ScrollContext(int line) : start(line), end(line) {}997ScrollContext(int _start, int _end) : start(_start), end(_end) {}998999void Offset(int offset) {1000start += offset;1001end += offset;1002}1003};10041005class FieldDelegate {1006public:1007virtual ~FieldDelegate() = default;10081009// Returns the number of lines needed to draw the field. The draw method will1010// be given a surface that have exactly this number of lines.1011virtual int FieldDelegateGetHeight() = 0;10121013// Returns the scroll context in the local coordinates of the field. By1014// default, the scroll context spans the whole field. Bigger fields with1015// internal navigation should override this method to provide a finer context.1016// Typical override methods would first get the scroll context of the internal1017// element then add the offset of the element in the field.1018virtual ScrollContext FieldDelegateGetScrollContext() {1019return ScrollContext(0, FieldDelegateGetHeight() - 1);1020}10211022// Draw the field in the given subpad surface. The surface have a height that1023// is equal to the height returned by FieldDelegateGetHeight(). If the field1024// is selected in the form window, then is_selected will be true.1025virtual void FieldDelegateDraw(Surface &surface, bool is_selected) = 0;10261027// Handle the key that wasn't handled by the form window or a container field.1028virtual HandleCharResult FieldDelegateHandleChar(int key) {1029return eKeyNotHandled;1030}10311032// This is executed once the user exists the field, that is, once the user1033// navigates to the next or the previous field. This is particularly useful to1034// do in-field validation and error setting. Fields with internal navigation1035// should call this method on their fields.1036virtual void FieldDelegateExitCallback() {}10371038// Fields may have internal navigation, for instance, a List Field have1039// multiple internal elements, which needs to be navigated. To allow for this1040// mechanism, the window shouldn't handle the navigation keys all the time,1041// and instead call the key handing method of the selected field. It should1042// only handle the navigation keys when the field contains a single element or1043// have the last or first element selected depending on if the user is1044// navigating forward or backward. Additionally, once a field is selected in1045// the forward or backward direction, its first or last internal element1046// should be selected. The following methods implements those mechanisms.10471048// Returns true if the first element in the field is selected or if the field1049// contains a single element.1050virtual bool FieldDelegateOnFirstOrOnlyElement() { return true; }10511052// Returns true if the last element in the field is selected or if the field1053// contains a single element.1054virtual bool FieldDelegateOnLastOrOnlyElement() { return true; }10551056// Select the first element in the field if multiple elements exists.1057virtual void FieldDelegateSelectFirstElement() {}10581059// Select the last element in the field if multiple elements exists.1060virtual void FieldDelegateSelectLastElement() {}10611062// Returns true if the field has an error, false otherwise.1063virtual bool FieldDelegateHasError() { return false; }10641065bool FieldDelegateIsVisible() { return m_is_visible; }10661067void FieldDelegateHide() { m_is_visible = false; }10681069void FieldDelegateShow() { m_is_visible = true; }10701071protected:1072bool m_is_visible = true;1073};10741075typedef std::unique_ptr<FieldDelegate> FieldDelegateUP;10761077class TextFieldDelegate : public FieldDelegate {1078public:1079TextFieldDelegate(const char *label, const char *content, bool required)1080: m_label(label), m_required(required) {1081if (content)1082m_content = content;1083}10841085// Text fields are drawn as titled boxes of a single line, with a possible1086// error messages at the end.1087//1088// __[Label]___________1089// | |1090// |__________________|1091// - Error message if it exists.10921093// The text field has a height of 3 lines. 2 lines for borders and 1 line for1094// the content.1095int GetFieldHeight() { return 3; }10961097// The text field has a full height of 3 or 4 lines. 3 lines for the actual1098// field and an optional line for an error if it exists.1099int FieldDelegateGetHeight() override {1100int height = GetFieldHeight();1101if (FieldDelegateHasError())1102height++;1103return height;1104}11051106// Get the cursor X position in the surface coordinate.1107int GetCursorXPosition() { return m_cursor_position - m_first_visibile_char; }11081109int GetContentLength() { return m_content.length(); }11101111void DrawContent(Surface &surface, bool is_selected) {1112UpdateScrolling(surface.GetWidth());11131114surface.MoveCursor(0, 0);1115const char *text = m_content.c_str() + m_first_visibile_char;1116surface.PutCString(text, surface.GetWidth());11171118// Highlight the cursor.1119surface.MoveCursor(GetCursorXPosition(), 0);1120if (is_selected)1121surface.AttributeOn(A_REVERSE);1122if (m_cursor_position == GetContentLength())1123// Cursor is past the last character. Highlight an empty space.1124surface.PutChar(' ');1125else1126surface.PutChar(m_content[m_cursor_position]);1127if (is_selected)1128surface.AttributeOff(A_REVERSE);1129}11301131void DrawField(Surface &surface, bool is_selected) {1132surface.TitledBox(m_label.c_str());11331134Rect content_bounds = surface.GetFrame();1135content_bounds.Inset(1, 1);1136Surface content_surface = surface.SubSurface(content_bounds);11371138DrawContent(content_surface, is_selected);1139}11401141void DrawError(Surface &surface) {1142if (!FieldDelegateHasError())1143return;1144surface.MoveCursor(0, 0);1145surface.AttributeOn(COLOR_PAIR(RedOnBlack));1146surface.PutChar(ACS_DIAMOND);1147surface.PutChar(' ');1148surface.PutCStringTruncated(1, GetError().c_str());1149surface.AttributeOff(COLOR_PAIR(RedOnBlack));1150}11511152void FieldDelegateDraw(Surface &surface, bool is_selected) override {1153Rect frame = surface.GetFrame();1154Rect field_bounds, error_bounds;1155frame.HorizontalSplit(GetFieldHeight(), field_bounds, error_bounds);1156Surface field_surface = surface.SubSurface(field_bounds);1157Surface error_surface = surface.SubSurface(error_bounds);11581159DrawField(field_surface, is_selected);1160DrawError(error_surface);1161}11621163// Get the position of the last visible character.1164int GetLastVisibleCharPosition(int width) {1165int position = m_first_visibile_char + width - 1;1166return std::min(position, GetContentLength());1167}11681169void UpdateScrolling(int width) {1170if (m_cursor_position < m_first_visibile_char) {1171m_first_visibile_char = m_cursor_position;1172return;1173}11741175if (m_cursor_position > GetLastVisibleCharPosition(width))1176m_first_visibile_char = m_cursor_position - (width - 1);1177}11781179// The cursor is allowed to move one character past the string.1180// m_cursor_position is in range [0, GetContentLength()].1181void MoveCursorRight() {1182if (m_cursor_position < GetContentLength())1183m_cursor_position++;1184}11851186void MoveCursorLeft() {1187if (m_cursor_position > 0)1188m_cursor_position--;1189}11901191void MoveCursorToStart() { m_cursor_position = 0; }11921193void MoveCursorToEnd() { m_cursor_position = GetContentLength(); }11941195void ScrollLeft() {1196if (m_first_visibile_char > 0)1197m_first_visibile_char--;1198}11991200// Insert a character at the current cursor position and advance the cursor1201// position.1202void InsertChar(char character) {1203m_content.insert(m_cursor_position, 1, character);1204m_cursor_position++;1205ClearError();1206}12071208// Remove the character before the cursor position, retreat the cursor1209// position, and scroll left.1210void RemovePreviousChar() {1211if (m_cursor_position == 0)1212return;12131214m_content.erase(m_cursor_position - 1, 1);1215m_cursor_position--;1216ScrollLeft();1217ClearError();1218}12191220// Remove the character after the cursor position.1221void RemoveNextChar() {1222if (m_cursor_position == GetContentLength())1223return;12241225m_content.erase(m_cursor_position, 1);1226ClearError();1227}12281229// Clear characters from the current cursor position to the end.1230void ClearToEnd() {1231m_content.erase(m_cursor_position);1232ClearError();1233}12341235void Clear() {1236m_content.clear();1237m_cursor_position = 0;1238ClearError();1239}12401241// True if the key represents a char that can be inserted in the field1242// content, false otherwise.1243virtual bool IsAcceptableChar(int key) {1244// The behavior of isprint is undefined when the value is not representable1245// as an unsigned char. So explicitly check for non-ascii key codes.1246if (key > 127)1247return false;1248return isprint(key);1249}12501251HandleCharResult FieldDelegateHandleChar(int key) override {1252if (IsAcceptableChar(key)) {1253ClearError();1254InsertChar((char)key);1255return eKeyHandled;1256}12571258switch (key) {1259case KEY_HOME:1260case KEY_CTRL_A:1261MoveCursorToStart();1262return eKeyHandled;1263case KEY_END:1264case KEY_CTRL_E:1265MoveCursorToEnd();1266return eKeyHandled;1267case KEY_RIGHT:1268case KEY_SF:1269MoveCursorRight();1270return eKeyHandled;1271case KEY_LEFT:1272case KEY_SR:1273MoveCursorLeft();1274return eKeyHandled;1275case KEY_BACKSPACE:1276case KEY_DELETE:1277RemovePreviousChar();1278return eKeyHandled;1279case KEY_DC:1280RemoveNextChar();1281return eKeyHandled;1282case KEY_EOL:1283case KEY_CTRL_K:1284ClearToEnd();1285return eKeyHandled;1286case KEY_DL:1287case KEY_CLEAR:1288Clear();1289return eKeyHandled;1290default:1291break;1292}1293return eKeyNotHandled;1294}12951296bool FieldDelegateHasError() override { return !m_error.empty(); }12971298void FieldDelegateExitCallback() override {1299if (!IsSpecified() && m_required)1300SetError("This field is required!");1301}13021303bool IsSpecified() { return !m_content.empty(); }13041305void ClearError() { m_error.clear(); }13061307const std::string &GetError() { return m_error; }13081309void SetError(const char *error) { m_error = error; }13101311const std::string &GetText() { return m_content; }13121313void SetText(const char *text) {1314if (text == nullptr) {1315m_content.clear();1316return;1317}1318m_content = text;1319}13201321protected:1322std::string m_label;1323bool m_required;1324// The position of the top left corner character of the border.1325std::string m_content;1326// The cursor position in the content string itself. Can be in the range1327// [0, GetContentLength()].1328int m_cursor_position = 0;1329// The index of the first visible character in the content.1330int m_first_visibile_char = 0;1331// Optional error message. If empty, field is considered to have no error.1332std::string m_error;1333};13341335class IntegerFieldDelegate : public TextFieldDelegate {1336public:1337IntegerFieldDelegate(const char *label, int content, bool required)1338: TextFieldDelegate(label, std::to_string(content).c_str(), required) {}13391340// Only accept digits.1341bool IsAcceptableChar(int key) override { return isdigit(key); }13421343// Returns the integer content of the field.1344int GetInteger() { return std::stoi(m_content); }1345};13461347class FileFieldDelegate : public TextFieldDelegate {1348public:1349FileFieldDelegate(const char *label, const char *content, bool need_to_exist,1350bool required)1351: TextFieldDelegate(label, content, required),1352m_need_to_exist(need_to_exist) {}13531354void FieldDelegateExitCallback() override {1355TextFieldDelegate::FieldDelegateExitCallback();1356if (!IsSpecified())1357return;13581359if (!m_need_to_exist)1360return;13611362FileSpec file = GetResolvedFileSpec();1363if (!FileSystem::Instance().Exists(file)) {1364SetError("File doesn't exist!");1365return;1366}1367if (FileSystem::Instance().IsDirectory(file)) {1368SetError("Not a file!");1369return;1370}1371}13721373FileSpec GetFileSpec() {1374FileSpec file_spec(GetPath());1375return file_spec;1376}13771378FileSpec GetResolvedFileSpec() {1379FileSpec file_spec(GetPath());1380FileSystem::Instance().Resolve(file_spec);1381return file_spec;1382}13831384const std::string &GetPath() { return m_content; }13851386protected:1387bool m_need_to_exist;1388};13891390class DirectoryFieldDelegate : public TextFieldDelegate {1391public:1392DirectoryFieldDelegate(const char *label, const char *content,1393bool need_to_exist, bool required)1394: TextFieldDelegate(label, content, required),1395m_need_to_exist(need_to_exist) {}13961397void FieldDelegateExitCallback() override {1398TextFieldDelegate::FieldDelegateExitCallback();1399if (!IsSpecified())1400return;14011402if (!m_need_to_exist)1403return;14041405FileSpec file = GetResolvedFileSpec();1406if (!FileSystem::Instance().Exists(file)) {1407SetError("Directory doesn't exist!");1408return;1409}1410if (!FileSystem::Instance().IsDirectory(file)) {1411SetError("Not a directory!");1412return;1413}1414}14151416FileSpec GetFileSpec() {1417FileSpec file_spec(GetPath());1418return file_spec;1419}14201421FileSpec GetResolvedFileSpec() {1422FileSpec file_spec(GetPath());1423FileSystem::Instance().Resolve(file_spec);1424return file_spec;1425}14261427const std::string &GetPath() { return m_content; }14281429protected:1430bool m_need_to_exist;1431};14321433class ArchFieldDelegate : public TextFieldDelegate {1434public:1435ArchFieldDelegate(const char *label, const char *content, bool required)1436: TextFieldDelegate(label, content, required) {}14371438void FieldDelegateExitCallback() override {1439TextFieldDelegate::FieldDelegateExitCallback();1440if (!IsSpecified())1441return;14421443if (!GetArchSpec().IsValid())1444SetError("Not a valid arch!");1445}14461447const std::string &GetArchString() { return m_content; }14481449ArchSpec GetArchSpec() { return ArchSpec(GetArchString()); }1450};14511452class BooleanFieldDelegate : public FieldDelegate {1453public:1454BooleanFieldDelegate(const char *label, bool content)1455: m_label(label), m_content(content) {}14561457// Boolean fields are drawn as checkboxes.1458//1459// [X] Label or [ ] Label14601461// Boolean fields are have a single line.1462int FieldDelegateGetHeight() override { return 1; }14631464void FieldDelegateDraw(Surface &surface, bool is_selected) override {1465surface.MoveCursor(0, 0);1466surface.PutChar('[');1467if (is_selected)1468surface.AttributeOn(A_REVERSE);1469surface.PutChar(m_content ? ACS_DIAMOND : ' ');1470if (is_selected)1471surface.AttributeOff(A_REVERSE);1472surface.PutChar(']');1473surface.PutChar(' ');1474surface.PutCString(m_label.c_str());1475}14761477void ToggleContent() { m_content = !m_content; }14781479void SetContentToTrue() { m_content = true; }14801481void SetContentToFalse() { m_content = false; }14821483HandleCharResult FieldDelegateHandleChar(int key) override {1484switch (key) {1485case 't':1486case '1':1487SetContentToTrue();1488return eKeyHandled;1489case 'f':1490case '0':1491SetContentToFalse();1492return eKeyHandled;1493case ' ':1494case '\r':1495case '\n':1496case KEY_ENTER:1497ToggleContent();1498return eKeyHandled;1499default:1500break;1501}1502return eKeyNotHandled;1503}15041505// Returns the boolean content of the field.1506bool GetBoolean() { return m_content; }15071508protected:1509std::string m_label;1510bool m_content;1511};15121513class ChoicesFieldDelegate : public FieldDelegate {1514public:1515ChoicesFieldDelegate(const char *label, int number_of_visible_choices,1516std::vector<std::string> choices)1517: m_label(label), m_number_of_visible_choices(number_of_visible_choices),1518m_choices(choices) {}15191520// Choices fields are drawn as titles boxses of a number of visible choices.1521// The rest of the choices become visible as the user scroll. The selected1522// choice is denoted by a diamond as the first character.1523//1524// __[Label]___________1525// |-Choice 1 |1526// | Choice 2 |1527// | Choice 3 |1528// |__________________|15291530// Choices field have two border characters plus the number of visible1531// choices.1532int FieldDelegateGetHeight() override {1533return m_number_of_visible_choices + 2;1534}15351536int GetNumberOfChoices() { return m_choices.size(); }15371538// Get the index of the last visible choice.1539int GetLastVisibleChoice() {1540int index = m_first_visibile_choice + m_number_of_visible_choices;1541return std::min(index, GetNumberOfChoices()) - 1;1542}15431544void DrawContent(Surface &surface, bool is_selected) {1545int choices_to_draw = GetLastVisibleChoice() - m_first_visibile_choice + 1;1546for (int i = 0; i < choices_to_draw; i++) {1547surface.MoveCursor(0, i);1548int current_choice = m_first_visibile_choice + i;1549const char *text = m_choices[current_choice].c_str();1550bool highlight = is_selected && current_choice == m_choice;1551if (highlight)1552surface.AttributeOn(A_REVERSE);1553surface.PutChar(current_choice == m_choice ? ACS_DIAMOND : ' ');1554surface.PutCString(text);1555if (highlight)1556surface.AttributeOff(A_REVERSE);1557}1558}15591560void FieldDelegateDraw(Surface &surface, bool is_selected) override {1561UpdateScrolling();15621563surface.TitledBox(m_label.c_str());15641565Rect content_bounds = surface.GetFrame();1566content_bounds.Inset(1, 1);1567Surface content_surface = surface.SubSurface(content_bounds);15681569DrawContent(content_surface, is_selected);1570}15711572void SelectPrevious() {1573if (m_choice > 0)1574m_choice--;1575}15761577void SelectNext() {1578if (m_choice < GetNumberOfChoices() - 1)1579m_choice++;1580}15811582void UpdateScrolling() {1583if (m_choice > GetLastVisibleChoice()) {1584m_first_visibile_choice = m_choice - (m_number_of_visible_choices - 1);1585return;1586}15871588if (m_choice < m_first_visibile_choice)1589m_first_visibile_choice = m_choice;1590}15911592HandleCharResult FieldDelegateHandleChar(int key) override {1593switch (key) {1594case KEY_UP:1595SelectPrevious();1596return eKeyHandled;1597case KEY_DOWN:1598SelectNext();1599return eKeyHandled;1600default:1601break;1602}1603return eKeyNotHandled;1604}16051606// Returns the content of the choice as a string.1607std::string GetChoiceContent() { return m_choices[m_choice]; }16081609// Returns the index of the choice.1610int GetChoice() { return m_choice; }16111612void SetChoice(llvm::StringRef choice) {1613for (int i = 0; i < GetNumberOfChoices(); i++) {1614if (choice == m_choices[i]) {1615m_choice = i;1616return;1617}1618}1619}16201621protected:1622std::string m_label;1623int m_number_of_visible_choices;1624std::vector<std::string> m_choices;1625// The index of the selected choice.1626int m_choice = 0;1627// The index of the first visible choice in the field.1628int m_first_visibile_choice = 0;1629};16301631class PlatformPluginFieldDelegate : public ChoicesFieldDelegate {1632public:1633PlatformPluginFieldDelegate(Debugger &debugger)1634: ChoicesFieldDelegate("Platform Plugin", 3, GetPossiblePluginNames()) {1635PlatformSP platform_sp = debugger.GetPlatformList().GetSelectedPlatform();1636if (platform_sp)1637SetChoice(platform_sp->GetPluginName());1638}16391640std::vector<std::string> GetPossiblePluginNames() {1641std::vector<std::string> names;1642size_t i = 0;1643for (llvm::StringRef name =1644PluginManager::GetPlatformPluginNameAtIndex(i++);1645!name.empty(); name = PluginManager::GetProcessPluginNameAtIndex(i++))1646names.push_back(name.str());1647return names;1648}16491650std::string GetPluginName() {1651std::string plugin_name = GetChoiceContent();1652return plugin_name;1653}1654};16551656class ProcessPluginFieldDelegate : public ChoicesFieldDelegate {1657public:1658ProcessPluginFieldDelegate()1659: ChoicesFieldDelegate("Process Plugin", 3, GetPossiblePluginNames()) {}16601661std::vector<std::string> GetPossiblePluginNames() {1662std::vector<std::string> names;1663names.push_back("<default>");16641665size_t i = 0;1666for (llvm::StringRef name = PluginManager::GetProcessPluginNameAtIndex(i++);1667!name.empty(); name = PluginManager::GetProcessPluginNameAtIndex(i++))1668names.push_back(name.str());1669return names;1670}16711672std::string GetPluginName() {1673std::string plugin_name = GetChoiceContent();1674if (plugin_name == "<default>")1675return "";1676return plugin_name;1677}1678};16791680class LazyBooleanFieldDelegate : public ChoicesFieldDelegate {1681public:1682LazyBooleanFieldDelegate(const char *label, const char *calculate_label)1683: ChoicesFieldDelegate(label, 3, GetPossibleOptions(calculate_label)) {}16841685static constexpr const char *kNo = "No";1686static constexpr const char *kYes = "Yes";16871688std::vector<std::string> GetPossibleOptions(const char *calculate_label) {1689std::vector<std::string> options;1690options.push_back(calculate_label);1691options.push_back(kYes);1692options.push_back(kNo);1693return options;1694}16951696LazyBool GetLazyBoolean() {1697std::string choice = GetChoiceContent();1698if (choice == kNo)1699return eLazyBoolNo;1700else if (choice == kYes)1701return eLazyBoolYes;1702else1703return eLazyBoolCalculate;1704}1705};17061707template <class T> class ListFieldDelegate : public FieldDelegate {1708public:1709ListFieldDelegate(const char *label, T default_field)1710: m_label(label), m_default_field(default_field),1711m_selection_type(SelectionType::NewButton) {}17121713// Signify which element is selected. If a field or a remove button is1714// selected, then m_selection_index signifies the particular field that1715// is selected or the field that the remove button belongs to.1716enum class SelectionType { Field, RemoveButton, NewButton };17171718// A List field is drawn as a titled box of a number of other fields of the1719// same type. Each field has a Remove button next to it that removes the1720// corresponding field. Finally, the last line contains a New button to add a1721// new field.1722//1723// __[Label]___________1724// | Field 0 [Remove] |1725// | Field 1 [Remove] |1726// | Field 2 [Remove] |1727// | [New] |1728// |__________________|17291730// List fields have two lines for border characters, 1 line for the New1731// button, and the total height of the available fields.1732int FieldDelegateGetHeight() override {1733// 2 border characters.1734int height = 2;1735// Total height of the fields.1736for (int i = 0; i < GetNumberOfFields(); i++) {1737height += m_fields[i].FieldDelegateGetHeight();1738}1739// A line for the New button.1740height++;1741return height;1742}17431744ScrollContext FieldDelegateGetScrollContext() override {1745int height = FieldDelegateGetHeight();1746if (m_selection_type == SelectionType::NewButton)1747return ScrollContext(height - 2, height - 1);17481749FieldDelegate &field = m_fields[m_selection_index];1750ScrollContext context = field.FieldDelegateGetScrollContext();17511752// Start at 1 because of the top border.1753int offset = 1;1754for (int i = 0; i < m_selection_index; i++) {1755offset += m_fields[i].FieldDelegateGetHeight();1756}1757context.Offset(offset);17581759// If the scroll context is touching the top border, include it in the1760// context to show the label.1761if (context.start == 1)1762context.start--;17631764// If the scroll context is touching the new button, include it as well as1765// the bottom border in the context.1766if (context.end == height - 3)1767context.end += 2;17681769return context;1770}17711772void DrawRemoveButton(Surface &surface, int highlight) {1773surface.MoveCursor(1, surface.GetHeight() / 2);1774if (highlight)1775surface.AttributeOn(A_REVERSE);1776surface.PutCString("[Remove]");1777if (highlight)1778surface.AttributeOff(A_REVERSE);1779}17801781void DrawFields(Surface &surface, bool is_selected) {1782int line = 0;1783int width = surface.GetWidth();1784for (int i = 0; i < GetNumberOfFields(); i++) {1785int height = m_fields[i].FieldDelegateGetHeight();1786Rect bounds = Rect(Point(0, line), Size(width, height));1787Rect field_bounds, remove_button_bounds;1788bounds.VerticalSplit(bounds.size.width - sizeof(" [Remove]"),1789field_bounds, remove_button_bounds);1790Surface field_surface = surface.SubSurface(field_bounds);1791Surface remove_button_surface = surface.SubSurface(remove_button_bounds);17921793bool is_element_selected = m_selection_index == i && is_selected;1794bool is_field_selected =1795is_element_selected && m_selection_type == SelectionType::Field;1796bool is_remove_button_selected =1797is_element_selected &&1798m_selection_type == SelectionType::RemoveButton;1799m_fields[i].FieldDelegateDraw(field_surface, is_field_selected);1800DrawRemoveButton(remove_button_surface, is_remove_button_selected);18011802line += height;1803}1804}18051806void DrawNewButton(Surface &surface, bool is_selected) {1807const char *button_text = "[New]";1808int x = (surface.GetWidth() - sizeof(button_text) - 1) / 2;1809surface.MoveCursor(x, 0);1810bool highlight =1811is_selected && m_selection_type == SelectionType::NewButton;1812if (highlight)1813surface.AttributeOn(A_REVERSE);1814surface.PutCString(button_text);1815if (highlight)1816surface.AttributeOff(A_REVERSE);1817}18181819void FieldDelegateDraw(Surface &surface, bool is_selected) override {1820surface.TitledBox(m_label.c_str());18211822Rect content_bounds = surface.GetFrame();1823content_bounds.Inset(1, 1);1824Rect fields_bounds, new_button_bounds;1825content_bounds.HorizontalSplit(content_bounds.size.height - 1,1826fields_bounds, new_button_bounds);1827Surface fields_surface = surface.SubSurface(fields_bounds);1828Surface new_button_surface = surface.SubSurface(new_button_bounds);18291830DrawFields(fields_surface, is_selected);1831DrawNewButton(new_button_surface, is_selected);1832}18331834void AddNewField() {1835m_fields.push_back(m_default_field);1836m_selection_index = GetNumberOfFields() - 1;1837m_selection_type = SelectionType::Field;1838FieldDelegate &field = m_fields[m_selection_index];1839field.FieldDelegateSelectFirstElement();1840}18411842void RemoveField() {1843m_fields.erase(m_fields.begin() + m_selection_index);1844if (m_selection_index != 0)1845m_selection_index--;18461847if (GetNumberOfFields() > 0) {1848m_selection_type = SelectionType::Field;1849FieldDelegate &field = m_fields[m_selection_index];1850field.FieldDelegateSelectFirstElement();1851} else1852m_selection_type = SelectionType::NewButton;1853}18541855HandleCharResult SelectNext(int key) {1856if (m_selection_type == SelectionType::NewButton)1857return eKeyNotHandled;18581859if (m_selection_type == SelectionType::RemoveButton) {1860if (m_selection_index == GetNumberOfFields() - 1) {1861m_selection_type = SelectionType::NewButton;1862return eKeyHandled;1863}1864m_selection_index++;1865m_selection_type = SelectionType::Field;1866FieldDelegate &next_field = m_fields[m_selection_index];1867next_field.FieldDelegateSelectFirstElement();1868return eKeyHandled;1869}18701871FieldDelegate &field = m_fields[m_selection_index];1872if (!field.FieldDelegateOnLastOrOnlyElement()) {1873return field.FieldDelegateHandleChar(key);1874}18751876field.FieldDelegateExitCallback();18771878m_selection_type = SelectionType::RemoveButton;1879return eKeyHandled;1880}18811882HandleCharResult SelectPrevious(int key) {1883if (FieldDelegateOnFirstOrOnlyElement())1884return eKeyNotHandled;18851886if (m_selection_type == SelectionType::RemoveButton) {1887m_selection_type = SelectionType::Field;1888FieldDelegate &field = m_fields[m_selection_index];1889field.FieldDelegateSelectLastElement();1890return eKeyHandled;1891}18921893if (m_selection_type == SelectionType::NewButton) {1894m_selection_type = SelectionType::RemoveButton;1895m_selection_index = GetNumberOfFields() - 1;1896return eKeyHandled;1897}18981899FieldDelegate &field = m_fields[m_selection_index];1900if (!field.FieldDelegateOnFirstOrOnlyElement()) {1901return field.FieldDelegateHandleChar(key);1902}19031904field.FieldDelegateExitCallback();19051906m_selection_type = SelectionType::RemoveButton;1907m_selection_index--;1908return eKeyHandled;1909}19101911// If the last element of the field is selected and it didn't handle the key.1912// Select the next field or new button if the selected field is the last one.1913HandleCharResult SelectNextInList(int key) {1914assert(m_selection_type == SelectionType::Field);19151916FieldDelegate &field = m_fields[m_selection_index];1917if (field.FieldDelegateHandleChar(key) == eKeyHandled)1918return eKeyHandled;19191920if (!field.FieldDelegateOnLastOrOnlyElement())1921return eKeyNotHandled;19221923field.FieldDelegateExitCallback();19241925if (m_selection_index == GetNumberOfFields() - 1) {1926m_selection_type = SelectionType::NewButton;1927return eKeyHandled;1928}19291930m_selection_index++;1931FieldDelegate &next_field = m_fields[m_selection_index];1932next_field.FieldDelegateSelectFirstElement();1933return eKeyHandled;1934}19351936HandleCharResult FieldDelegateHandleChar(int key) override {1937switch (key) {1938case '\r':1939case '\n':1940case KEY_ENTER:1941switch (m_selection_type) {1942case SelectionType::NewButton:1943AddNewField();1944return eKeyHandled;1945case SelectionType::RemoveButton:1946RemoveField();1947return eKeyHandled;1948case SelectionType::Field:1949return SelectNextInList(key);1950}1951break;1952case '\t':1953return SelectNext(key);1954case KEY_SHIFT_TAB:1955return SelectPrevious(key);1956default:1957break;1958}19591960// If the key wasn't handled and one of the fields is selected, pass the key1961// to that field.1962if (m_selection_type == SelectionType::Field) {1963return m_fields[m_selection_index].FieldDelegateHandleChar(key);1964}19651966return eKeyNotHandled;1967}19681969bool FieldDelegateOnLastOrOnlyElement() override {1970if (m_selection_type == SelectionType::NewButton) {1971return true;1972}1973return false;1974}19751976bool FieldDelegateOnFirstOrOnlyElement() override {1977if (m_selection_type == SelectionType::NewButton &&1978GetNumberOfFields() == 0)1979return true;19801981if (m_selection_type == SelectionType::Field && m_selection_index == 0) {1982FieldDelegate &field = m_fields[m_selection_index];1983return field.FieldDelegateOnFirstOrOnlyElement();1984}19851986return false;1987}19881989void FieldDelegateSelectFirstElement() override {1990if (GetNumberOfFields() == 0) {1991m_selection_type = SelectionType::NewButton;1992return;1993}19941995m_selection_type = SelectionType::Field;1996m_selection_index = 0;1997}19981999void FieldDelegateSelectLastElement() override {2000m_selection_type = SelectionType::NewButton;2001}20022003int GetNumberOfFields() { return m_fields.size(); }20042005// Returns the form delegate at the current index.2006T &GetField(int index) { return m_fields[index]; }20072008protected:2009std::string m_label;2010// The default field delegate instance from which new field delegates will be2011// created though a copy.2012T m_default_field;2013std::vector<T> m_fields;2014int m_selection_index = 0;2015// See SelectionType class enum.2016SelectionType m_selection_type;2017};20182019class ArgumentsFieldDelegate : public ListFieldDelegate<TextFieldDelegate> {2020public:2021ArgumentsFieldDelegate()2022: ListFieldDelegate("Arguments",2023TextFieldDelegate("Argument", "", false)) {}20242025Args GetArguments() {2026Args arguments;2027for (int i = 0; i < GetNumberOfFields(); i++) {2028arguments.AppendArgument(GetField(i).GetText());2029}2030return arguments;2031}20322033void AddArguments(const Args &arguments) {2034for (size_t i = 0; i < arguments.GetArgumentCount(); i++) {2035AddNewField();2036TextFieldDelegate &field = GetField(GetNumberOfFields() - 1);2037field.SetText(arguments.GetArgumentAtIndex(i));2038}2039}2040};20412042template <class KeyFieldDelegateType, class ValueFieldDelegateType>2043class MappingFieldDelegate : public FieldDelegate {2044public:2045MappingFieldDelegate(KeyFieldDelegateType key_field,2046ValueFieldDelegateType value_field)2047: m_key_field(key_field), m_value_field(value_field),2048m_selection_type(SelectionType::Key) {}20492050// Signify which element is selected. The key field or its value field.2051enum class SelectionType { Key, Value };20522053// A mapping field is drawn as two text fields with a right arrow in between.2054// The first field stores the key of the mapping and the second stores the2055// value if the mapping.2056//2057// __[Key]_____________ __[Value]___________2058// | | > | |2059// |__________________| |__________________|2060// - Error message if it exists.20612062// The mapping field has a height that is equal to the maximum height between2063// the key and value fields.2064int FieldDelegateGetHeight() override {2065return std::max(m_key_field.FieldDelegateGetHeight(),2066m_value_field.FieldDelegateGetHeight());2067}20682069void DrawArrow(Surface &surface) {2070surface.MoveCursor(0, 1);2071surface.PutChar(ACS_RARROW);2072}20732074void FieldDelegateDraw(Surface &surface, bool is_selected) override {2075Rect bounds = surface.GetFrame();2076Rect key_field_bounds, arrow_and_value_field_bounds;2077bounds.VerticalSplit(bounds.size.width / 2, key_field_bounds,2078arrow_and_value_field_bounds);2079Rect arrow_bounds, value_field_bounds;2080arrow_and_value_field_bounds.VerticalSplit(1, arrow_bounds,2081value_field_bounds);20822083Surface key_field_surface = surface.SubSurface(key_field_bounds);2084Surface arrow_surface = surface.SubSurface(arrow_bounds);2085Surface value_field_surface = surface.SubSurface(value_field_bounds);20862087bool key_is_selected =2088m_selection_type == SelectionType::Key && is_selected;2089m_key_field.FieldDelegateDraw(key_field_surface, key_is_selected);2090DrawArrow(arrow_surface);2091bool value_is_selected =2092m_selection_type == SelectionType::Value && is_selected;2093m_value_field.FieldDelegateDraw(value_field_surface, value_is_selected);2094}20952096HandleCharResult SelectNext(int key) {2097if (FieldDelegateOnLastOrOnlyElement())2098return eKeyNotHandled;20992100if (!m_key_field.FieldDelegateOnLastOrOnlyElement()) {2101return m_key_field.FieldDelegateHandleChar(key);2102}21032104m_key_field.FieldDelegateExitCallback();2105m_selection_type = SelectionType::Value;2106m_value_field.FieldDelegateSelectFirstElement();2107return eKeyHandled;2108}21092110HandleCharResult SelectPrevious(int key) {2111if (FieldDelegateOnFirstOrOnlyElement())2112return eKeyNotHandled;21132114if (!m_value_field.FieldDelegateOnFirstOrOnlyElement()) {2115return m_value_field.FieldDelegateHandleChar(key);2116}21172118m_value_field.FieldDelegateExitCallback();2119m_selection_type = SelectionType::Key;2120m_key_field.FieldDelegateSelectLastElement();2121return eKeyHandled;2122}21232124// If the value field is selected, pass the key to it. If the key field is2125// selected, its last element is selected, and it didn't handle the key, then2126// select its corresponding value field.2127HandleCharResult SelectNextField(int key) {2128if (m_selection_type == SelectionType::Value) {2129return m_value_field.FieldDelegateHandleChar(key);2130}21312132if (m_key_field.FieldDelegateHandleChar(key) == eKeyHandled)2133return eKeyHandled;21342135if (!m_key_field.FieldDelegateOnLastOrOnlyElement())2136return eKeyNotHandled;21372138m_key_field.FieldDelegateExitCallback();2139m_selection_type = SelectionType::Value;2140m_value_field.FieldDelegateSelectFirstElement();2141return eKeyHandled;2142}21432144HandleCharResult FieldDelegateHandleChar(int key) override {2145switch (key) {2146case KEY_RETURN:2147return SelectNextField(key);2148case '\t':2149return SelectNext(key);2150case KEY_SHIFT_TAB:2151return SelectPrevious(key);2152default:2153break;2154}21552156// If the key wasn't handled, pass the key to the selected field.2157if (m_selection_type == SelectionType::Key)2158return m_key_field.FieldDelegateHandleChar(key);2159else2160return m_value_field.FieldDelegateHandleChar(key);21612162return eKeyNotHandled;2163}21642165bool FieldDelegateOnFirstOrOnlyElement() override {2166return m_selection_type == SelectionType::Key;2167}21682169bool FieldDelegateOnLastOrOnlyElement() override {2170return m_selection_type == SelectionType::Value;2171}21722173void FieldDelegateSelectFirstElement() override {2174m_selection_type = SelectionType::Key;2175}21762177void FieldDelegateSelectLastElement() override {2178m_selection_type = SelectionType::Value;2179}21802181bool FieldDelegateHasError() override {2182return m_key_field.FieldDelegateHasError() ||2183m_value_field.FieldDelegateHasError();2184}21852186KeyFieldDelegateType &GetKeyField() { return m_key_field; }21872188ValueFieldDelegateType &GetValueField() { return m_value_field; }21892190protected:2191KeyFieldDelegateType m_key_field;2192ValueFieldDelegateType m_value_field;2193// See SelectionType class enum.2194SelectionType m_selection_type;2195};21962197class EnvironmentVariableNameFieldDelegate : public TextFieldDelegate {2198public:2199EnvironmentVariableNameFieldDelegate(const char *content)2200: TextFieldDelegate("Name", content, true) {}22012202// Environment variable names can't contain an equal sign.2203bool IsAcceptableChar(int key) override {2204return TextFieldDelegate::IsAcceptableChar(key) && key != '=';2205}22062207const std::string &GetName() { return m_content; }2208};22092210class EnvironmentVariableFieldDelegate2211: public MappingFieldDelegate<EnvironmentVariableNameFieldDelegate,2212TextFieldDelegate> {2213public:2214EnvironmentVariableFieldDelegate()2215: MappingFieldDelegate(2216EnvironmentVariableNameFieldDelegate(""),2217TextFieldDelegate("Value", "", /*required=*/false)) {}22182219const std::string &GetName() { return GetKeyField().GetName(); }22202221const std::string &GetValue() { return GetValueField().GetText(); }22222223void SetName(const char *name) { return GetKeyField().SetText(name); }22242225void SetValue(const char *value) { return GetValueField().SetText(value); }2226};22272228class EnvironmentVariableListFieldDelegate2229: public ListFieldDelegate<EnvironmentVariableFieldDelegate> {2230public:2231EnvironmentVariableListFieldDelegate(const char *label)2232: ListFieldDelegate(label, EnvironmentVariableFieldDelegate()) {}22332234Environment GetEnvironment() {2235Environment environment;2236for (int i = 0; i < GetNumberOfFields(); i++) {2237environment.insert(2238std::make_pair(GetField(i).GetName(), GetField(i).GetValue()));2239}2240return environment;2241}22422243void AddEnvironmentVariables(const Environment &environment) {2244for (auto &variable : environment) {2245AddNewField();2246EnvironmentVariableFieldDelegate &field =2247GetField(GetNumberOfFields() - 1);2248field.SetName(variable.getKey().str().c_str());2249field.SetValue(variable.getValue().c_str());2250}2251}2252};22532254class FormAction {2255public:2256FormAction(const char *label, std::function<void(Window &)> action)2257: m_action(action) {2258if (label)2259m_label = label;2260}22612262// Draw a centered [Label].2263void Draw(Surface &surface, bool is_selected) {2264int x = (surface.GetWidth() - m_label.length()) / 2;2265surface.MoveCursor(x, 0);2266if (is_selected)2267surface.AttributeOn(A_REVERSE);2268surface.PutChar('[');2269surface.PutCString(m_label.c_str());2270surface.PutChar(']');2271if (is_selected)2272surface.AttributeOff(A_REVERSE);2273}22742275void Execute(Window &window) { m_action(window); }22762277const std::string &GetLabel() { return m_label; }22782279protected:2280std::string m_label;2281std::function<void(Window &)> m_action;2282};22832284class FormDelegate {2285public:2286FormDelegate() = default;22872288virtual ~FormDelegate() = default;22892290virtual std::string GetName() = 0;22912292virtual void UpdateFieldsVisibility() {}22932294FieldDelegate *GetField(uint32_t field_index) {2295if (field_index < m_fields.size())2296return m_fields[field_index].get();2297return nullptr;2298}22992300FormAction &GetAction(int action_index) { return m_actions[action_index]; }23012302int GetNumberOfFields() { return m_fields.size(); }23032304int GetNumberOfActions() { return m_actions.size(); }23052306bool HasError() { return !m_error.empty(); }23072308void ClearError() { m_error.clear(); }23092310const std::string &GetError() { return m_error; }23112312void SetError(const char *error) { m_error = error; }23132314// If all fields are valid, true is returned. Otherwise, an error message is2315// set and false is returned. This method is usually called at the start of an2316// action that requires valid fields.2317bool CheckFieldsValidity() {2318for (int i = 0; i < GetNumberOfFields(); i++) {2319GetField(i)->FieldDelegateExitCallback();2320if (GetField(i)->FieldDelegateHasError()) {2321SetError("Some fields are invalid!");2322return false;2323}2324}2325return true;2326}23272328// Factory methods to create and add fields of specific types.23292330TextFieldDelegate *AddTextField(const char *label, const char *content,2331bool required) {2332TextFieldDelegate *delegate =2333new TextFieldDelegate(label, content, required);2334m_fields.push_back(FieldDelegateUP(delegate));2335return delegate;2336}23372338FileFieldDelegate *AddFileField(const char *label, const char *content,2339bool need_to_exist, bool required) {2340FileFieldDelegate *delegate =2341new FileFieldDelegate(label, content, need_to_exist, required);2342m_fields.push_back(FieldDelegateUP(delegate));2343return delegate;2344}23452346DirectoryFieldDelegate *AddDirectoryField(const char *label,2347const char *content,2348bool need_to_exist, bool required) {2349DirectoryFieldDelegate *delegate =2350new DirectoryFieldDelegate(label, content, need_to_exist, required);2351m_fields.push_back(FieldDelegateUP(delegate));2352return delegate;2353}23542355ArchFieldDelegate *AddArchField(const char *label, const char *content,2356bool required) {2357ArchFieldDelegate *delegate =2358new ArchFieldDelegate(label, content, required);2359m_fields.push_back(FieldDelegateUP(delegate));2360return delegate;2361}23622363IntegerFieldDelegate *AddIntegerField(const char *label, int content,2364bool required) {2365IntegerFieldDelegate *delegate =2366new IntegerFieldDelegate(label, content, required);2367m_fields.push_back(FieldDelegateUP(delegate));2368return delegate;2369}23702371BooleanFieldDelegate *AddBooleanField(const char *label, bool content) {2372BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content);2373m_fields.push_back(FieldDelegateUP(delegate));2374return delegate;2375}23762377LazyBooleanFieldDelegate *AddLazyBooleanField(const char *label,2378const char *calculate_label) {2379LazyBooleanFieldDelegate *delegate =2380new LazyBooleanFieldDelegate(label, calculate_label);2381m_fields.push_back(FieldDelegateUP(delegate));2382return delegate;2383}23842385ChoicesFieldDelegate *AddChoicesField(const char *label, int height,2386std::vector<std::string> choices) {2387ChoicesFieldDelegate *delegate =2388new ChoicesFieldDelegate(label, height, choices);2389m_fields.push_back(FieldDelegateUP(delegate));2390return delegate;2391}23922393PlatformPluginFieldDelegate *AddPlatformPluginField(Debugger &debugger) {2394PlatformPluginFieldDelegate *delegate =2395new PlatformPluginFieldDelegate(debugger);2396m_fields.push_back(FieldDelegateUP(delegate));2397return delegate;2398}23992400ProcessPluginFieldDelegate *AddProcessPluginField() {2401ProcessPluginFieldDelegate *delegate = new ProcessPluginFieldDelegate();2402m_fields.push_back(FieldDelegateUP(delegate));2403return delegate;2404}24052406template <class T>2407ListFieldDelegate<T> *AddListField(const char *label, T default_field) {2408ListFieldDelegate<T> *delegate =2409new ListFieldDelegate<T>(label, default_field);2410m_fields.push_back(FieldDelegateUP(delegate));2411return delegate;2412}24132414ArgumentsFieldDelegate *AddArgumentsField() {2415ArgumentsFieldDelegate *delegate = new ArgumentsFieldDelegate();2416m_fields.push_back(FieldDelegateUP(delegate));2417return delegate;2418}24192420template <class K, class V>2421MappingFieldDelegate<K, V> *AddMappingField(K key_field, V value_field) {2422MappingFieldDelegate<K, V> *delegate =2423new MappingFieldDelegate<K, V>(key_field, value_field);2424m_fields.push_back(FieldDelegateUP(delegate));2425return delegate;2426}24272428EnvironmentVariableNameFieldDelegate *2429AddEnvironmentVariableNameField(const char *content) {2430EnvironmentVariableNameFieldDelegate *delegate =2431new EnvironmentVariableNameFieldDelegate(content);2432m_fields.push_back(FieldDelegateUP(delegate));2433return delegate;2434}24352436EnvironmentVariableFieldDelegate *AddEnvironmentVariableField() {2437EnvironmentVariableFieldDelegate *delegate =2438new EnvironmentVariableFieldDelegate();2439m_fields.push_back(FieldDelegateUP(delegate));2440return delegate;2441}24422443EnvironmentVariableListFieldDelegate *2444AddEnvironmentVariableListField(const char *label) {2445EnvironmentVariableListFieldDelegate *delegate =2446new EnvironmentVariableListFieldDelegate(label);2447m_fields.push_back(FieldDelegateUP(delegate));2448return delegate;2449}24502451// Factory methods for adding actions.24522453void AddAction(const char *label, std::function<void(Window &)> action) {2454m_actions.push_back(FormAction(label, action));2455}24562457protected:2458std::vector<FieldDelegateUP> m_fields;2459std::vector<FormAction> m_actions;2460// Optional error message. If empty, form is considered to have no error.2461std::string m_error;2462};24632464typedef std::shared_ptr<FormDelegate> FormDelegateSP;24652466class FormWindowDelegate : public WindowDelegate {2467public:2468FormWindowDelegate(FormDelegateSP &delegate_sp) : m_delegate_sp(delegate_sp) {2469assert(m_delegate_sp->GetNumberOfActions() > 0);2470if (m_delegate_sp->GetNumberOfFields() > 0)2471m_selection_type = SelectionType::Field;2472else2473m_selection_type = SelectionType::Action;2474}24752476// Signify which element is selected. If a field or an action is selected,2477// then m_selection_index signifies the particular field or action that is2478// selected.2479enum class SelectionType { Field, Action };24802481// A form window is padded by one character from all sides. First, if an error2482// message exists, it is drawn followed by a separator. Then one or more2483// fields are drawn. Finally, all available actions are drawn on a single2484// line.2485//2486// ___<Form Name>_________________________________________________2487// | |2488// | - Error message if it exists. |2489// |-------------------------------------------------------------|2490// | Form elements here. |2491// | Form actions here. |2492// | |2493// |______________________________________[Press Esc to cancel]__|2494//24952496// One line for the error and another for the horizontal line.2497int GetErrorHeight() {2498if (m_delegate_sp->HasError())2499return 2;2500return 0;2501}25022503// Actions span a single line.2504int GetActionsHeight() {2505if (m_delegate_sp->GetNumberOfActions() > 0)2506return 1;2507return 0;2508}25092510// Get the total number of needed lines to draw the contents.2511int GetContentHeight() {2512int height = 0;2513height += GetErrorHeight();2514for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {2515if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())2516continue;2517height += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();2518}2519height += GetActionsHeight();2520return height;2521}25222523ScrollContext GetScrollContext() {2524if (m_selection_type == SelectionType::Action)2525return ScrollContext(GetContentHeight() - 1);25262527FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);2528ScrollContext context = field->FieldDelegateGetScrollContext();25292530int offset = GetErrorHeight();2531for (int i = 0; i < m_selection_index; i++) {2532if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())2533continue;2534offset += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();2535}2536context.Offset(offset);25372538// If the context is touching the error, include the error in the context as2539// well.2540if (context.start == GetErrorHeight())2541context.start = 0;25422543return context;2544}25452546void UpdateScrolling(Surface &surface) {2547ScrollContext context = GetScrollContext();2548int content_height = GetContentHeight();2549int surface_height = surface.GetHeight();2550int visible_height = std::min(content_height, surface_height);2551int last_visible_line = m_first_visible_line + visible_height - 1;25522553// If the last visible line is bigger than the content, then it is invalid2554// and needs to be set to the last line in the content. This can happen when2555// a field has shrunk in height.2556if (last_visible_line > content_height - 1) {2557m_first_visible_line = content_height - visible_height;2558}25592560if (context.start < m_first_visible_line) {2561m_first_visible_line = context.start;2562return;2563}25642565if (context.end > last_visible_line) {2566m_first_visible_line = context.end - visible_height + 1;2567}2568}25692570void DrawError(Surface &surface) {2571if (!m_delegate_sp->HasError())2572return;2573surface.MoveCursor(0, 0);2574surface.AttributeOn(COLOR_PAIR(RedOnBlack));2575surface.PutChar(ACS_DIAMOND);2576surface.PutChar(' ');2577surface.PutCStringTruncated(1, m_delegate_sp->GetError().c_str());2578surface.AttributeOff(COLOR_PAIR(RedOnBlack));25792580surface.MoveCursor(0, 1);2581surface.HorizontalLine(surface.GetWidth());2582}25832584void DrawFields(Surface &surface) {2585int line = 0;2586int width = surface.GetWidth();2587bool a_field_is_selected = m_selection_type == SelectionType::Field;2588for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {2589FieldDelegate *field = m_delegate_sp->GetField(i);2590if (!field->FieldDelegateIsVisible())2591continue;2592bool is_field_selected = a_field_is_selected && m_selection_index == i;2593int height = field->FieldDelegateGetHeight();2594Rect bounds = Rect(Point(0, line), Size(width, height));2595Surface field_surface = surface.SubSurface(bounds);2596field->FieldDelegateDraw(field_surface, is_field_selected);2597line += height;2598}2599}26002601void DrawActions(Surface &surface) {2602int number_of_actions = m_delegate_sp->GetNumberOfActions();2603int width = surface.GetWidth() / number_of_actions;2604bool an_action_is_selected = m_selection_type == SelectionType::Action;2605int x = 0;2606for (int i = 0; i < number_of_actions; i++) {2607bool is_action_selected = an_action_is_selected && m_selection_index == i;2608FormAction &action = m_delegate_sp->GetAction(i);2609Rect bounds = Rect(Point(x, 0), Size(width, 1));2610Surface action_surface = surface.SubSurface(bounds);2611action.Draw(action_surface, is_action_selected);2612x += width;2613}2614}26152616void DrawElements(Surface &surface) {2617Rect frame = surface.GetFrame();2618Rect fields_bounds, actions_bounds;2619frame.HorizontalSplit(surface.GetHeight() - GetActionsHeight(),2620fields_bounds, actions_bounds);2621Surface fields_surface = surface.SubSurface(fields_bounds);2622Surface actions_surface = surface.SubSurface(actions_bounds);26232624DrawFields(fields_surface);2625DrawActions(actions_surface);2626}26272628// Contents are first drawn on a pad. Then a subset of that pad is copied to2629// the derived window starting at the first visible line. This essentially2630// provides scrolling functionality.2631void DrawContent(Surface &surface) {2632UpdateScrolling(surface);26332634int width = surface.GetWidth();2635int height = GetContentHeight();2636Pad pad = Pad(Size(width, height));26372638Rect frame = pad.GetFrame();2639Rect error_bounds, elements_bounds;2640frame.HorizontalSplit(GetErrorHeight(), error_bounds, elements_bounds);2641Surface error_surface = pad.SubSurface(error_bounds);2642Surface elements_surface = pad.SubSurface(elements_bounds);26432644DrawError(error_surface);2645DrawElements(elements_surface);26462647int copy_height = std::min(surface.GetHeight(), pad.GetHeight());2648pad.CopyToSurface(surface, Point(0, m_first_visible_line), Point(),2649Size(width, copy_height));2650}26512652void DrawSubmitHint(Surface &surface, bool is_active) {2653surface.MoveCursor(2, surface.GetHeight() - 1);2654if (is_active)2655surface.AttributeOn(A_BOLD | COLOR_PAIR(BlackOnWhite));2656surface.Printf("[Press Alt+Enter to %s]",2657m_delegate_sp->GetAction(0).GetLabel().c_str());2658if (is_active)2659surface.AttributeOff(A_BOLD | COLOR_PAIR(BlackOnWhite));2660}26612662bool WindowDelegateDraw(Window &window, bool force) override {2663m_delegate_sp->UpdateFieldsVisibility();26642665window.Erase();26662667window.DrawTitleBox(m_delegate_sp->GetName().c_str(),2668"Press Esc to Cancel");2669DrawSubmitHint(window, window.IsActive());26702671Rect content_bounds = window.GetFrame();2672content_bounds.Inset(2, 2);2673Surface content_surface = window.SubSurface(content_bounds);26742675DrawContent(content_surface);2676return true;2677}26782679void SkipNextHiddenFields() {2680while (true) {2681if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())2682return;26832684if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {2685m_selection_type = SelectionType::Action;2686m_selection_index = 0;2687return;2688}26892690m_selection_index++;2691}2692}26932694HandleCharResult SelectNext(int key) {2695if (m_selection_type == SelectionType::Action) {2696if (m_selection_index < m_delegate_sp->GetNumberOfActions() - 1) {2697m_selection_index++;2698return eKeyHandled;2699}27002701m_selection_index = 0;2702m_selection_type = SelectionType::Field;2703SkipNextHiddenFields();2704if (m_selection_type == SelectionType::Field) {2705FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);2706next_field->FieldDelegateSelectFirstElement();2707}2708return eKeyHandled;2709}27102711FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);2712if (!field->FieldDelegateOnLastOrOnlyElement()) {2713return field->FieldDelegateHandleChar(key);2714}27152716field->FieldDelegateExitCallback();27172718if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {2719m_selection_type = SelectionType::Action;2720m_selection_index = 0;2721return eKeyHandled;2722}27232724m_selection_index++;2725SkipNextHiddenFields();27262727if (m_selection_type == SelectionType::Field) {2728FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);2729next_field->FieldDelegateSelectFirstElement();2730}27312732return eKeyHandled;2733}27342735void SkipPreviousHiddenFields() {2736while (true) {2737if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())2738return;27392740if (m_selection_index == 0) {2741m_selection_type = SelectionType::Action;2742m_selection_index = 0;2743return;2744}27452746m_selection_index--;2747}2748}27492750HandleCharResult SelectPrevious(int key) {2751if (m_selection_type == SelectionType::Action) {2752if (m_selection_index > 0) {2753m_selection_index--;2754return eKeyHandled;2755}2756m_selection_index = m_delegate_sp->GetNumberOfFields() - 1;2757m_selection_type = SelectionType::Field;2758SkipPreviousHiddenFields();2759if (m_selection_type == SelectionType::Field) {2760FieldDelegate *previous_field =2761m_delegate_sp->GetField(m_selection_index);2762previous_field->FieldDelegateSelectLastElement();2763}2764return eKeyHandled;2765}27662767FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);2768if (!field->FieldDelegateOnFirstOrOnlyElement()) {2769return field->FieldDelegateHandleChar(key);2770}27712772field->FieldDelegateExitCallback();27732774if (m_selection_index == 0) {2775m_selection_type = SelectionType::Action;2776m_selection_index = m_delegate_sp->GetNumberOfActions() - 1;2777return eKeyHandled;2778}27792780m_selection_index--;2781SkipPreviousHiddenFields();27822783if (m_selection_type == SelectionType::Field) {2784FieldDelegate *previous_field =2785m_delegate_sp->GetField(m_selection_index);2786previous_field->FieldDelegateSelectLastElement();2787}27882789return eKeyHandled;2790}27912792void ExecuteAction(Window &window, int index) {2793FormAction &action = m_delegate_sp->GetAction(index);2794action.Execute(window);2795if (m_delegate_sp->HasError()) {2796m_first_visible_line = 0;2797m_selection_index = 0;2798m_selection_type = SelectionType::Field;2799}2800}28012802// Always return eKeyHandled to absorb all events since forms are always2803// added as pop-ups that should take full control until canceled or submitted.2804HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {2805switch (key) {2806case '\r':2807case '\n':2808case KEY_ENTER:2809if (m_selection_type == SelectionType::Action) {2810ExecuteAction(window, m_selection_index);2811return eKeyHandled;2812}2813break;2814case KEY_ALT_ENTER:2815ExecuteAction(window, 0);2816return eKeyHandled;2817case '\t':2818SelectNext(key);2819return eKeyHandled;2820case KEY_SHIFT_TAB:2821SelectPrevious(key);2822return eKeyHandled;2823case KEY_ESCAPE:2824window.GetParent()->RemoveSubWindow(&window);2825return eKeyHandled;2826default:2827break;2828}28292830// If the key wasn't handled and one of the fields is selected, pass the key2831// to that field.2832if (m_selection_type == SelectionType::Field) {2833FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);2834if (field->FieldDelegateHandleChar(key) == eKeyHandled)2835return eKeyHandled;2836}28372838// If the key wasn't handled by the possibly selected field, handle some2839// extra keys for navigation.2840switch (key) {2841case KEY_DOWN:2842SelectNext(key);2843return eKeyHandled;2844case KEY_UP:2845SelectPrevious(key);2846return eKeyHandled;2847default:2848break;2849}28502851return eKeyHandled;2852}28532854protected:2855FormDelegateSP m_delegate_sp;2856// The index of the currently selected SelectionType.2857int m_selection_index = 0;2858// See SelectionType class enum.2859SelectionType m_selection_type;2860// The first visible line from the pad.2861int m_first_visible_line = 0;2862};28632864///////////////////////////2865// Form Delegate Instances2866///////////////////////////28672868class DetachOrKillProcessFormDelegate : public FormDelegate {2869public:2870DetachOrKillProcessFormDelegate(Process *process) : m_process(process) {2871SetError("There is a running process, either detach or kill it.");28722873m_keep_stopped_field =2874AddBooleanField("Keep process stopped when detaching.", false);28752876AddAction("Detach", [this](Window &window) { Detach(window); });2877AddAction("Kill", [this](Window &window) { Kill(window); });2878}28792880std::string GetName() override { return "Detach/Kill Process"; }28812882void Kill(Window &window) {2883Status destroy_status(m_process->Destroy(false));2884if (destroy_status.Fail()) {2885SetError("Failed to kill process.");2886return;2887}2888window.GetParent()->RemoveSubWindow(&window);2889}28902891void Detach(Window &window) {2892Status detach_status(m_process->Detach(m_keep_stopped_field->GetBoolean()));2893if (detach_status.Fail()) {2894SetError("Failed to detach from process.");2895return;2896}2897window.GetParent()->RemoveSubWindow(&window);2898}28992900protected:2901Process *m_process;2902BooleanFieldDelegate *m_keep_stopped_field;2903};29042905class ProcessAttachFormDelegate : public FormDelegate {2906public:2907ProcessAttachFormDelegate(Debugger &debugger, WindowSP main_window_sp)2908: m_debugger(debugger), m_main_window_sp(main_window_sp) {2909std::vector<std::string> types;2910types.push_back(std::string("Name"));2911types.push_back(std::string("PID"));2912m_type_field = AddChoicesField("Attach By", 2, types);2913m_pid_field = AddIntegerField("PID", 0, true);2914m_name_field =2915AddTextField("Process Name", GetDefaultProcessName().c_str(), true);2916m_continue_field = AddBooleanField("Continue once attached.", false);2917m_wait_for_field = AddBooleanField("Wait for process to launch.", false);2918m_include_existing_field =2919AddBooleanField("Include existing processes.", false);2920m_show_advanced_field = AddBooleanField("Show advanced settings.", false);2921m_plugin_field = AddProcessPluginField();29222923AddAction("Attach", [this](Window &window) { Attach(window); });2924}29252926std::string GetName() override { return "Attach Process"; }29272928void UpdateFieldsVisibility() override {2929if (m_type_field->GetChoiceContent() == "Name") {2930m_pid_field->FieldDelegateHide();2931m_name_field->FieldDelegateShow();2932m_wait_for_field->FieldDelegateShow();2933if (m_wait_for_field->GetBoolean())2934m_include_existing_field->FieldDelegateShow();2935else2936m_include_existing_field->FieldDelegateHide();2937} else {2938m_pid_field->FieldDelegateShow();2939m_name_field->FieldDelegateHide();2940m_wait_for_field->FieldDelegateHide();2941m_include_existing_field->FieldDelegateHide();2942}2943if (m_show_advanced_field->GetBoolean())2944m_plugin_field->FieldDelegateShow();2945else2946m_plugin_field->FieldDelegateHide();2947}29482949// Get the basename of the target's main executable if available, empty string2950// otherwise.2951std::string GetDefaultProcessName() {2952Target *target = m_debugger.GetSelectedTarget().get();2953if (target == nullptr)2954return "";29552956ModuleSP module_sp = target->GetExecutableModule();2957if (!module_sp->IsExecutable())2958return "";29592960return module_sp->GetFileSpec().GetFilename().AsCString();2961}29622963bool StopRunningProcess() {2964ExecutionContext exe_ctx =2965m_debugger.GetCommandInterpreter().GetExecutionContext();29662967if (!exe_ctx.HasProcessScope())2968return false;29692970Process *process = exe_ctx.GetProcessPtr();2971if (!(process && process->IsAlive()))2972return false;29732974FormDelegateSP form_delegate_sp =2975FormDelegateSP(new DetachOrKillProcessFormDelegate(process));2976Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);2977WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(2978form_delegate_sp->GetName().c_str(), bounds, true);2979WindowDelegateSP window_delegate_sp =2980WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));2981form_window_sp->SetDelegate(window_delegate_sp);29822983return true;2984}29852986Target *GetTarget() {2987Target *target = m_debugger.GetSelectedTarget().get();29882989if (target != nullptr)2990return target;29912992TargetSP new_target_sp;2993m_debugger.GetTargetList().CreateTarget(2994m_debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp);29952996target = new_target_sp.get();29972998if (target == nullptr)2999SetError("Failed to create target.");30003001m_debugger.GetTargetList().SetSelectedTarget(new_target_sp);30023003return target;3004}30053006ProcessAttachInfo GetAttachInfo() {3007ProcessAttachInfo attach_info;3008attach_info.SetContinueOnceAttached(m_continue_field->GetBoolean());3009if (m_type_field->GetChoiceContent() == "Name") {3010attach_info.GetExecutableFile().SetFile(m_name_field->GetText(),3011FileSpec::Style::native);3012attach_info.SetWaitForLaunch(m_wait_for_field->GetBoolean());3013if (m_wait_for_field->GetBoolean())3014attach_info.SetIgnoreExisting(!m_include_existing_field->GetBoolean());3015} else {3016attach_info.SetProcessID(m_pid_field->GetInteger());3017}3018attach_info.SetProcessPluginName(m_plugin_field->GetPluginName());30193020return attach_info;3021}30223023void Attach(Window &window) {3024ClearError();30253026bool all_fields_are_valid = CheckFieldsValidity();3027if (!all_fields_are_valid)3028return;30293030bool process_is_running = StopRunningProcess();3031if (process_is_running)3032return;30333034Target *target = GetTarget();3035if (HasError())3036return;30373038StreamString stream;3039ProcessAttachInfo attach_info = GetAttachInfo();3040Status status = target->Attach(attach_info, &stream);30413042if (status.Fail()) {3043SetError(status.AsCString());3044return;3045}30463047ProcessSP process_sp(target->GetProcessSP());3048if (!process_sp) {3049SetError("Attached sucessfully but target has no process.");3050return;3051}30523053if (attach_info.GetContinueOnceAttached())3054process_sp->Resume();30553056window.GetParent()->RemoveSubWindow(&window);3057}30583059protected:3060Debugger &m_debugger;3061WindowSP m_main_window_sp;30623063ChoicesFieldDelegate *m_type_field;3064IntegerFieldDelegate *m_pid_field;3065TextFieldDelegate *m_name_field;3066BooleanFieldDelegate *m_continue_field;3067BooleanFieldDelegate *m_wait_for_field;3068BooleanFieldDelegate *m_include_existing_field;3069BooleanFieldDelegate *m_show_advanced_field;3070ProcessPluginFieldDelegate *m_plugin_field;3071};30723073class TargetCreateFormDelegate : public FormDelegate {3074public:3075TargetCreateFormDelegate(Debugger &debugger) : m_debugger(debugger) {3076m_executable_field = AddFileField("Executable", "", /*need_to_exist=*/true,3077/*required=*/true);3078m_core_file_field = AddFileField("Core File", "", /*need_to_exist=*/true,3079/*required=*/false);3080m_symbol_file_field = AddFileField(3081"Symbol File", "", /*need_to_exist=*/true, /*required=*/false);3082m_show_advanced_field = AddBooleanField("Show advanced settings.", false);3083m_remote_file_field = AddFileField(3084"Remote File", "", /*need_to_exist=*/false, /*required=*/false);3085m_arch_field = AddArchField("Architecture", "", /*required=*/false);3086m_platform_field = AddPlatformPluginField(debugger);3087m_load_dependent_files_field =3088AddChoicesField("Load Dependents", 3, GetLoadDependentFilesChoices());30893090AddAction("Create", [this](Window &window) { CreateTarget(window); });3091}30923093std::string GetName() override { return "Create Target"; }30943095void UpdateFieldsVisibility() override {3096if (m_show_advanced_field->GetBoolean()) {3097m_remote_file_field->FieldDelegateShow();3098m_arch_field->FieldDelegateShow();3099m_platform_field->FieldDelegateShow();3100m_load_dependent_files_field->FieldDelegateShow();3101} else {3102m_remote_file_field->FieldDelegateHide();3103m_arch_field->FieldDelegateHide();3104m_platform_field->FieldDelegateHide();3105m_load_dependent_files_field->FieldDelegateHide();3106}3107}31083109static constexpr const char *kLoadDependentFilesNo = "No";3110static constexpr const char *kLoadDependentFilesYes = "Yes";3111static constexpr const char *kLoadDependentFilesExecOnly = "Executable only";31123113std::vector<std::string> GetLoadDependentFilesChoices() {3114std::vector<std::string> load_dependents_options;3115load_dependents_options.push_back(kLoadDependentFilesExecOnly);3116load_dependents_options.push_back(kLoadDependentFilesYes);3117load_dependents_options.push_back(kLoadDependentFilesNo);3118return load_dependents_options;3119}31203121LoadDependentFiles GetLoadDependentFiles() {3122std::string choice = m_load_dependent_files_field->GetChoiceContent();3123if (choice == kLoadDependentFilesNo)3124return eLoadDependentsNo;3125if (choice == kLoadDependentFilesYes)3126return eLoadDependentsYes;3127return eLoadDependentsDefault;3128}31293130OptionGroupPlatform GetPlatformOptions() {3131OptionGroupPlatform platform_options(false);3132platform_options.SetPlatformName(m_platform_field->GetPluginName().c_str());3133return platform_options;3134}31353136TargetSP GetTarget() {3137OptionGroupPlatform platform_options = GetPlatformOptions();3138TargetSP target_sp;3139Status status = m_debugger.GetTargetList().CreateTarget(3140m_debugger, m_executable_field->GetPath(),3141m_arch_field->GetArchString(), GetLoadDependentFiles(),3142&platform_options, target_sp);31433144if (status.Fail()) {3145SetError(status.AsCString());3146return nullptr;3147}31483149m_debugger.GetTargetList().SetSelectedTarget(target_sp);31503151return target_sp;3152}31533154void SetSymbolFile(TargetSP target_sp) {3155if (!m_symbol_file_field->IsSpecified())3156return;31573158ModuleSP module_sp(target_sp->GetExecutableModule());3159if (!module_sp)3160return;31613162module_sp->SetSymbolFileFileSpec(3163m_symbol_file_field->GetResolvedFileSpec());3164}31653166void SetCoreFile(TargetSP target_sp) {3167if (!m_core_file_field->IsSpecified())3168return;31693170FileSpec core_file_spec = m_core_file_field->GetResolvedFileSpec();31713172FileSpec core_file_directory_spec;3173core_file_directory_spec.SetDirectory(core_file_spec.GetDirectory());3174target_sp->AppendExecutableSearchPaths(core_file_directory_spec);31753176ProcessSP process_sp(target_sp->CreateProcess(3177m_debugger.GetListener(), llvm::StringRef(), &core_file_spec, false));31783179if (!process_sp) {3180SetError("Unknown core file format!");3181return;3182}31833184Status status = process_sp->LoadCore();3185if (status.Fail()) {3186SetError("Unknown core file format!");3187return;3188}3189}31903191void SetRemoteFile(TargetSP target_sp) {3192if (!m_remote_file_field->IsSpecified())3193return;31943195ModuleSP module_sp(target_sp->GetExecutableModule());3196if (!module_sp)3197return;31983199FileSpec remote_file_spec = m_remote_file_field->GetFileSpec();3200module_sp->SetPlatformFileSpec(remote_file_spec);3201}32023203void RemoveTarget(TargetSP target_sp) {3204m_debugger.GetTargetList().DeleteTarget(target_sp);3205}32063207void CreateTarget(Window &window) {3208ClearError();32093210bool all_fields_are_valid = CheckFieldsValidity();3211if (!all_fields_are_valid)3212return;32133214TargetSP target_sp = GetTarget();3215if (HasError())3216return;32173218SetSymbolFile(target_sp);3219if (HasError()) {3220RemoveTarget(target_sp);3221return;3222}32233224SetCoreFile(target_sp);3225if (HasError()) {3226RemoveTarget(target_sp);3227return;3228}32293230SetRemoteFile(target_sp);3231if (HasError()) {3232RemoveTarget(target_sp);3233return;3234}32353236window.GetParent()->RemoveSubWindow(&window);3237}32383239protected:3240Debugger &m_debugger;32413242FileFieldDelegate *m_executable_field;3243FileFieldDelegate *m_core_file_field;3244FileFieldDelegate *m_symbol_file_field;3245BooleanFieldDelegate *m_show_advanced_field;3246FileFieldDelegate *m_remote_file_field;3247ArchFieldDelegate *m_arch_field;3248PlatformPluginFieldDelegate *m_platform_field;3249ChoicesFieldDelegate *m_load_dependent_files_field;3250};32513252class ProcessLaunchFormDelegate : public FormDelegate {3253public:3254ProcessLaunchFormDelegate(Debugger &debugger, WindowSP main_window_sp)3255: m_debugger(debugger), m_main_window_sp(main_window_sp) {32563257m_arguments_field = AddArgumentsField();3258SetArgumentsFieldDefaultValue();3259m_target_environment_field =3260AddEnvironmentVariableListField("Target Environment Variables");3261SetTargetEnvironmentFieldDefaultValue();3262m_working_directory_field = AddDirectoryField(3263"Working Directory", GetDefaultWorkingDirectory().c_str(), true, false);32643265m_show_advanced_field = AddBooleanField("Show advanced settings.", false);32663267m_stop_at_entry_field = AddBooleanField("Stop at entry point.", false);3268m_detach_on_error_field =3269AddBooleanField("Detach on error.", GetDefaultDetachOnError());3270m_disable_aslr_field =3271AddBooleanField("Disable ASLR", GetDefaultDisableASLR());3272m_plugin_field = AddProcessPluginField();3273m_arch_field = AddArchField("Architecture", "", false);3274m_shell_field = AddFileField("Shell", "", true, false);3275m_expand_shell_arguments_field =3276AddBooleanField("Expand shell arguments.", false);32773278m_disable_standard_io_field =3279AddBooleanField("Disable Standard IO", GetDefaultDisableStandardIO());3280m_standard_output_field =3281AddFileField("Standard Output File", "", /*need_to_exist=*/false,3282/*required=*/false);3283m_standard_error_field =3284AddFileField("Standard Error File", "", /*need_to_exist=*/false,3285/*required=*/false);3286m_standard_input_field =3287AddFileField("Standard Input File", "", /*need_to_exist=*/false,3288/*required=*/false);32893290m_show_inherited_environment_field =3291AddBooleanField("Show inherited environment variables.", false);3292m_inherited_environment_field =3293AddEnvironmentVariableListField("Inherited Environment Variables");3294SetInheritedEnvironmentFieldDefaultValue();32953296AddAction("Launch", [this](Window &window) { Launch(window); });3297}32983299std::string GetName() override { return "Launch Process"; }33003301void UpdateFieldsVisibility() override {3302if (m_show_advanced_field->GetBoolean()) {3303m_stop_at_entry_field->FieldDelegateShow();3304m_detach_on_error_field->FieldDelegateShow();3305m_disable_aslr_field->FieldDelegateShow();3306m_plugin_field->FieldDelegateShow();3307m_arch_field->FieldDelegateShow();3308m_shell_field->FieldDelegateShow();3309m_expand_shell_arguments_field->FieldDelegateShow();3310m_disable_standard_io_field->FieldDelegateShow();3311if (m_disable_standard_io_field->GetBoolean()) {3312m_standard_input_field->FieldDelegateHide();3313m_standard_output_field->FieldDelegateHide();3314m_standard_error_field->FieldDelegateHide();3315} else {3316m_standard_input_field->FieldDelegateShow();3317m_standard_output_field->FieldDelegateShow();3318m_standard_error_field->FieldDelegateShow();3319}3320m_show_inherited_environment_field->FieldDelegateShow();3321if (m_show_inherited_environment_field->GetBoolean())3322m_inherited_environment_field->FieldDelegateShow();3323else3324m_inherited_environment_field->FieldDelegateHide();3325} else {3326m_stop_at_entry_field->FieldDelegateHide();3327m_detach_on_error_field->FieldDelegateHide();3328m_disable_aslr_field->FieldDelegateHide();3329m_plugin_field->FieldDelegateHide();3330m_arch_field->FieldDelegateHide();3331m_shell_field->FieldDelegateHide();3332m_expand_shell_arguments_field->FieldDelegateHide();3333m_disable_standard_io_field->FieldDelegateHide();3334m_standard_input_field->FieldDelegateHide();3335m_standard_output_field->FieldDelegateHide();3336m_standard_error_field->FieldDelegateHide();3337m_show_inherited_environment_field->FieldDelegateHide();3338m_inherited_environment_field->FieldDelegateHide();3339}3340}33413342// Methods for setting the default value of the fields.33433344void SetArgumentsFieldDefaultValue() {3345TargetSP target = m_debugger.GetSelectedTarget();3346if (target == nullptr)3347return;33483349const Args &target_arguments =3350target->GetProcessLaunchInfo().GetArguments();3351m_arguments_field->AddArguments(target_arguments);3352}33533354void SetTargetEnvironmentFieldDefaultValue() {3355TargetSP target = m_debugger.GetSelectedTarget();3356if (target == nullptr)3357return;33583359const Environment &target_environment = target->GetTargetEnvironment();3360m_target_environment_field->AddEnvironmentVariables(target_environment);3361}33623363void SetInheritedEnvironmentFieldDefaultValue() {3364TargetSP target = m_debugger.GetSelectedTarget();3365if (target == nullptr)3366return;33673368const Environment &inherited_environment =3369target->GetInheritedEnvironment();3370m_inherited_environment_field->AddEnvironmentVariables(3371inherited_environment);3372}33733374std::string GetDefaultWorkingDirectory() {3375TargetSP target = m_debugger.GetSelectedTarget();3376if (target == nullptr)3377return "";33783379PlatformSP platform = target->GetPlatform();3380return platform->GetWorkingDirectory().GetPath();3381}33823383bool GetDefaultDisableASLR() {3384TargetSP target = m_debugger.GetSelectedTarget();3385if (target == nullptr)3386return false;33873388return target->GetDisableASLR();3389}33903391bool GetDefaultDisableStandardIO() {3392TargetSP target = m_debugger.GetSelectedTarget();3393if (target == nullptr)3394return true;33953396return target->GetDisableSTDIO();3397}33983399bool GetDefaultDetachOnError() {3400TargetSP target = m_debugger.GetSelectedTarget();3401if (target == nullptr)3402return true;34033404return target->GetDetachOnError();3405}34063407// Methods for getting the necessary information and setting them to the3408// ProcessLaunchInfo.34093410void GetExecutableSettings(ProcessLaunchInfo &launch_info) {3411TargetSP target = m_debugger.GetSelectedTarget();3412ModuleSP executable_module = target->GetExecutableModule();3413llvm::StringRef target_settings_argv0 = target->GetArg0();34143415if (!target_settings_argv0.empty()) {3416launch_info.GetArguments().AppendArgument(target_settings_argv0);3417launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(),3418false);3419return;3420}34213422launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(),3423true);3424}34253426void GetArguments(ProcessLaunchInfo &launch_info) {3427TargetSP target = m_debugger.GetSelectedTarget();3428Args arguments = m_arguments_field->GetArguments();3429launch_info.GetArguments().AppendArguments(arguments);3430}34313432void GetEnvironment(ProcessLaunchInfo &launch_info) {3433Environment target_environment =3434m_target_environment_field->GetEnvironment();3435Environment inherited_environment =3436m_inherited_environment_field->GetEnvironment();3437launch_info.GetEnvironment().insert(target_environment.begin(),3438target_environment.end());3439launch_info.GetEnvironment().insert(inherited_environment.begin(),3440inherited_environment.end());3441}34423443void GetWorkingDirectory(ProcessLaunchInfo &launch_info) {3444if (m_working_directory_field->IsSpecified())3445launch_info.SetWorkingDirectory(3446m_working_directory_field->GetResolvedFileSpec());3447}34483449void GetStopAtEntry(ProcessLaunchInfo &launch_info) {3450if (m_stop_at_entry_field->GetBoolean())3451launch_info.GetFlags().Set(eLaunchFlagStopAtEntry);3452else3453launch_info.GetFlags().Clear(eLaunchFlagStopAtEntry);3454}34553456void GetDetachOnError(ProcessLaunchInfo &launch_info) {3457if (m_detach_on_error_field->GetBoolean())3458launch_info.GetFlags().Set(eLaunchFlagDetachOnError);3459else3460launch_info.GetFlags().Clear(eLaunchFlagDetachOnError);3461}34623463void GetDisableASLR(ProcessLaunchInfo &launch_info) {3464if (m_disable_aslr_field->GetBoolean())3465launch_info.GetFlags().Set(eLaunchFlagDisableASLR);3466else3467launch_info.GetFlags().Clear(eLaunchFlagDisableASLR);3468}34693470void GetPlugin(ProcessLaunchInfo &launch_info) {3471launch_info.SetProcessPluginName(m_plugin_field->GetPluginName());3472}34733474void GetArch(ProcessLaunchInfo &launch_info) {3475if (!m_arch_field->IsSpecified())3476return;34773478TargetSP target_sp = m_debugger.GetSelectedTarget();3479PlatformSP platform_sp =3480target_sp ? target_sp->GetPlatform() : PlatformSP();3481launch_info.GetArchitecture() = Platform::GetAugmentedArchSpec(3482platform_sp.get(), m_arch_field->GetArchString());3483}34843485void GetShell(ProcessLaunchInfo &launch_info) {3486if (!m_shell_field->IsSpecified())3487return;34883489launch_info.SetShell(m_shell_field->GetResolvedFileSpec());3490launch_info.SetShellExpandArguments(3491m_expand_shell_arguments_field->GetBoolean());3492}34933494void GetStandardIO(ProcessLaunchInfo &launch_info) {3495if (m_disable_standard_io_field->GetBoolean()) {3496launch_info.GetFlags().Set(eLaunchFlagDisableSTDIO);3497return;3498}34993500FileAction action;3501if (m_standard_input_field->IsSpecified()) {3502if (action.Open(STDIN_FILENO, m_standard_input_field->GetFileSpec(), true,3503false))3504launch_info.AppendFileAction(action);3505}3506if (m_standard_output_field->IsSpecified()) {3507if (action.Open(STDOUT_FILENO, m_standard_output_field->GetFileSpec(),3508false, true))3509launch_info.AppendFileAction(action);3510}3511if (m_standard_error_field->IsSpecified()) {3512if (action.Open(STDERR_FILENO, m_standard_error_field->GetFileSpec(),3513false, true))3514launch_info.AppendFileAction(action);3515}3516}35173518void GetInheritTCC(ProcessLaunchInfo &launch_info) {3519if (m_debugger.GetSelectedTarget()->GetInheritTCC())3520launch_info.GetFlags().Set(eLaunchFlagInheritTCCFromParent);3521}35223523ProcessLaunchInfo GetLaunchInfo() {3524ProcessLaunchInfo launch_info;35253526GetExecutableSettings(launch_info);3527GetArguments(launch_info);3528GetEnvironment(launch_info);3529GetWorkingDirectory(launch_info);3530GetStopAtEntry(launch_info);3531GetDetachOnError(launch_info);3532GetDisableASLR(launch_info);3533GetPlugin(launch_info);3534GetArch(launch_info);3535GetShell(launch_info);3536GetStandardIO(launch_info);3537GetInheritTCC(launch_info);35383539return launch_info;3540}35413542bool StopRunningProcess() {3543ExecutionContext exe_ctx =3544m_debugger.GetCommandInterpreter().GetExecutionContext();35453546if (!exe_ctx.HasProcessScope())3547return false;35483549Process *process = exe_ctx.GetProcessPtr();3550if (!(process && process->IsAlive()))3551return false;35523553FormDelegateSP form_delegate_sp =3554FormDelegateSP(new DetachOrKillProcessFormDelegate(process));3555Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);3556WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(3557form_delegate_sp->GetName().c_str(), bounds, true);3558WindowDelegateSP window_delegate_sp =3559WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));3560form_window_sp->SetDelegate(window_delegate_sp);35613562return true;3563}35643565Target *GetTarget() {3566Target *target = m_debugger.GetSelectedTarget().get();35673568if (target == nullptr) {3569SetError("No target exists!");3570return nullptr;3571}35723573ModuleSP exe_module_sp = target->GetExecutableModule();35743575if (exe_module_sp == nullptr) {3576SetError("No executable in target!");3577return nullptr;3578}35793580return target;3581}35823583void Launch(Window &window) {3584ClearError();35853586bool all_fields_are_valid = CheckFieldsValidity();3587if (!all_fields_are_valid)3588return;35893590bool process_is_running = StopRunningProcess();3591if (process_is_running)3592return;35933594Target *target = GetTarget();3595if (HasError())3596return;35973598StreamString stream;3599ProcessLaunchInfo launch_info = GetLaunchInfo();3600Status status = target->Launch(launch_info, &stream);36013602if (status.Fail()) {3603SetError(status.AsCString());3604return;3605}36063607ProcessSP process_sp(target->GetProcessSP());3608if (!process_sp) {3609SetError("Launched successfully but target has no process!");3610return;3611}36123613window.GetParent()->RemoveSubWindow(&window);3614}36153616protected:3617Debugger &m_debugger;3618WindowSP m_main_window_sp;36193620ArgumentsFieldDelegate *m_arguments_field;3621EnvironmentVariableListFieldDelegate *m_target_environment_field;3622DirectoryFieldDelegate *m_working_directory_field;36233624BooleanFieldDelegate *m_show_advanced_field;36253626BooleanFieldDelegate *m_stop_at_entry_field;3627BooleanFieldDelegate *m_detach_on_error_field;3628BooleanFieldDelegate *m_disable_aslr_field;3629ProcessPluginFieldDelegate *m_plugin_field;3630ArchFieldDelegate *m_arch_field;3631FileFieldDelegate *m_shell_field;3632BooleanFieldDelegate *m_expand_shell_arguments_field;3633BooleanFieldDelegate *m_disable_standard_io_field;3634FileFieldDelegate *m_standard_input_field;3635FileFieldDelegate *m_standard_output_field;3636FileFieldDelegate *m_standard_error_field;36373638BooleanFieldDelegate *m_show_inherited_environment_field;3639EnvironmentVariableListFieldDelegate *m_inherited_environment_field;3640};36413642////////////3643// Searchers3644////////////36453646class SearcherDelegate {3647public:3648SearcherDelegate() = default;36493650virtual ~SearcherDelegate() = default;36513652virtual int GetNumberOfMatches() = 0;36533654// Get the string that will be displayed for the match at the input index.3655virtual const std::string &GetMatchTextAtIndex(int index) = 0;36563657// Update the matches of the search. This is executed every time the text3658// field handles an event.3659virtual void UpdateMatches(const std::string &text) = 0;36603661// Execute the user callback given the index of some match. This is executed3662// once the user selects a match.3663virtual void ExecuteCallback(int match_index) = 0;3664};36653666typedef std::shared_ptr<SearcherDelegate> SearcherDelegateSP;36673668class SearcherWindowDelegate : public WindowDelegate {3669public:3670SearcherWindowDelegate(SearcherDelegateSP &delegate_sp)3671: m_delegate_sp(delegate_sp), m_text_field("Search", "", false) {3672;3673}36743675// A completion window is padded by one character from all sides. A text field3676// is first drawn for inputting the searcher request, then a list of matches3677// are displayed in a scrollable list.3678//3679// ___<Searcher Window Name>____________________________3680// | |3681// | __[Search]_______________________________________ |3682// | | | |3683// | |_______________________________________________| |3684// | - Match 1. |3685// | - Match 2. |3686// | - ... |3687// | |3688// |____________________________[Press Esc to Cancel]__|3689//36903691// Get the index of the last visible match. Assuming at least one match3692// exists.3693int GetLastVisibleMatch(int height) {3694int index = m_first_visible_match + height;3695return std::min(index, m_delegate_sp->GetNumberOfMatches()) - 1;3696}36973698int GetNumberOfVisibleMatches(int height) {3699return GetLastVisibleMatch(height) - m_first_visible_match + 1;3700}37013702void UpdateScrolling(Surface &surface) {3703if (m_selected_match < m_first_visible_match) {3704m_first_visible_match = m_selected_match;3705return;3706}37073708int height = surface.GetHeight();3709int last_visible_match = GetLastVisibleMatch(height);3710if (m_selected_match > last_visible_match) {3711m_first_visible_match = m_selected_match - height + 1;3712}3713}37143715void DrawMatches(Surface &surface) {3716if (m_delegate_sp->GetNumberOfMatches() == 0)3717return;37183719UpdateScrolling(surface);37203721int count = GetNumberOfVisibleMatches(surface.GetHeight());3722for (int i = 0; i < count; i++) {3723surface.MoveCursor(1, i);3724int current_match = m_first_visible_match + i;3725if (current_match == m_selected_match)3726surface.AttributeOn(A_REVERSE);3727surface.PutCString(3728m_delegate_sp->GetMatchTextAtIndex(current_match).c_str());3729if (current_match == m_selected_match)3730surface.AttributeOff(A_REVERSE);3731}3732}37333734void DrawContent(Surface &surface) {3735Rect content_bounds = surface.GetFrame();3736Rect text_field_bounds, matchs_bounds;3737content_bounds.HorizontalSplit(m_text_field.FieldDelegateGetHeight(),3738text_field_bounds, matchs_bounds);3739Surface text_field_surface = surface.SubSurface(text_field_bounds);3740Surface matches_surface = surface.SubSurface(matchs_bounds);37413742m_text_field.FieldDelegateDraw(text_field_surface, true);3743DrawMatches(matches_surface);3744}37453746bool WindowDelegateDraw(Window &window, bool force) override {3747window.Erase();37483749window.DrawTitleBox(window.GetName(), "Press Esc to Cancel");37503751Rect content_bounds = window.GetFrame();3752content_bounds.Inset(2, 2);3753Surface content_surface = window.SubSurface(content_bounds);37543755DrawContent(content_surface);3756return true;3757}37583759void SelectNext() {3760if (m_selected_match != m_delegate_sp->GetNumberOfMatches() - 1)3761m_selected_match++;3762}37633764void SelectPrevious() {3765if (m_selected_match != 0)3766m_selected_match--;3767}37683769void ExecuteCallback(Window &window) {3770m_delegate_sp->ExecuteCallback(m_selected_match);3771window.GetParent()->RemoveSubWindow(&window);3772}37733774void UpdateMatches() {3775m_delegate_sp->UpdateMatches(m_text_field.GetText());3776m_selected_match = 0;3777}37783779HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {3780switch (key) {3781case '\r':3782case '\n':3783case KEY_ENTER:3784ExecuteCallback(window);3785return eKeyHandled;3786case '\t':3787case KEY_DOWN:3788SelectNext();3789return eKeyHandled;3790case KEY_SHIFT_TAB:3791case KEY_UP:3792SelectPrevious();3793return eKeyHandled;3794case KEY_ESCAPE:3795window.GetParent()->RemoveSubWindow(&window);3796return eKeyHandled;3797default:3798break;3799}38003801if (m_text_field.FieldDelegateHandleChar(key) == eKeyHandled)3802UpdateMatches();38033804return eKeyHandled;3805}38063807protected:3808SearcherDelegateSP m_delegate_sp;3809TextFieldDelegate m_text_field;3810// The index of the currently selected match.3811int m_selected_match = 0;3812// The index of the first visible match.3813int m_first_visible_match = 0;3814};38153816//////////////////////////////3817// Searcher Delegate Instances3818//////////////////////////////38193820// This is a searcher delegate wrapper around CommandCompletions common3821// callbacks. The callbacks are only given the match string. The completion_mask3822// can be a combination of lldb::CompletionType.3823class CommonCompletionSearcherDelegate : public SearcherDelegate {3824public:3825typedef std::function<void(const std::string &)> CallbackType;38263827CommonCompletionSearcherDelegate(Debugger &debugger, uint32_t completion_mask,3828CallbackType callback)3829: m_debugger(debugger), m_completion_mask(completion_mask),3830m_callback(callback) {}38313832int GetNumberOfMatches() override { return m_matches.GetSize(); }38333834const std::string &GetMatchTextAtIndex(int index) override {3835return m_matches[index];3836}38373838void UpdateMatches(const std::string &text) override {3839CompletionResult result;3840CompletionRequest request(text.c_str(), text.size(), result);3841lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(3842m_debugger.GetCommandInterpreter(), m_completion_mask, request,3843nullptr);3844result.GetMatches(m_matches);3845}38463847void ExecuteCallback(int match_index) override {3848m_callback(m_matches[match_index]);3849}38503851protected:3852Debugger &m_debugger;3853// A compound mask from lldb::CompletionType.3854uint32_t m_completion_mask;3855// A callback to execute once the user selects a match. The match is passed to3856// the callback as a string.3857CallbackType m_callback;3858StringList m_matches;3859};38603861////////3862// Menus3863////////38643865class MenuDelegate {3866public:3867virtual ~MenuDelegate() = default;38683869virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0;3870};38713872class Menu : public WindowDelegate {3873public:3874enum class Type { Invalid, Bar, Item, Separator };38753876// Menubar or separator constructor3877Menu(Type type);38783879// Menuitem constructor3880Menu(const char *name, const char *key_name, int key_value,3881uint64_t identifier);38823883~Menu() override = default;38843885const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; }38863887void SetDelegate(const MenuDelegateSP &delegate_sp) {3888m_delegate_sp = delegate_sp;3889}38903891void RecalculateNameLengths();38923893void AddSubmenu(const MenuSP &menu_sp);38943895int DrawAndRunMenu(Window &window);38963897void DrawMenuTitle(Window &window, bool highlight);38983899bool WindowDelegateDraw(Window &window, bool force) override;39003901HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;39023903MenuActionResult ActionPrivate(Menu &menu) {3904MenuActionResult result = MenuActionResult::NotHandled;3905if (m_delegate_sp) {3906result = m_delegate_sp->MenuDelegateAction(menu);3907if (result != MenuActionResult::NotHandled)3908return result;3909} else if (m_parent) {3910result = m_parent->ActionPrivate(menu);3911if (result != MenuActionResult::NotHandled)3912return result;3913}3914return m_canned_result;3915}39163917MenuActionResult Action() {3918// Call the recursive action so it can try to handle it with the menu3919// delegate, and if not, try our parent menu3920return ActionPrivate(*this);3921}39223923void SetCannedResult(MenuActionResult result) { m_canned_result = result; }39243925Menus &GetSubmenus() { return m_submenus; }39263927const Menus &GetSubmenus() const { return m_submenus; }39283929int GetSelectedSubmenuIndex() const { return m_selected; }39303931void SetSelectedSubmenuIndex(int idx) { m_selected = idx; }39323933Type GetType() const { return m_type; }39343935int GetStartingColumn() const { return m_start_col; }39363937void SetStartingColumn(int col) { m_start_col = col; }39383939int GetKeyValue() const { return m_key_value; }39403941std::string &GetName() { return m_name; }39423943int GetDrawWidth() const {3944return m_max_submenu_name_length + m_max_submenu_key_name_length + 8;3945}39463947uint64_t GetIdentifier() const { return m_identifier; }39483949void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }39503951protected:3952std::string m_name;3953std::string m_key_name;3954uint64_t m_identifier;3955Type m_type;3956int m_key_value;3957int m_start_col;3958int m_max_submenu_name_length;3959int m_max_submenu_key_name_length;3960int m_selected;3961Menu *m_parent;3962Menus m_submenus;3963WindowSP m_menu_window_sp;3964MenuActionResult m_canned_result;3965MenuDelegateSP m_delegate_sp;3966};39673968// Menubar or separator constructor3969Menu::Menu(Type type)3970: m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0),3971m_start_col(0), m_max_submenu_name_length(0),3972m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),3973m_submenus(), m_canned_result(MenuActionResult::NotHandled),3974m_delegate_sp() {}39753976// Menuitem constructor3977Menu::Menu(const char *name, const char *key_name, int key_value,3978uint64_t identifier)3979: m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid),3980m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0),3981m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),3982m_submenus(), m_canned_result(MenuActionResult::NotHandled),3983m_delegate_sp() {3984if (name && name[0]) {3985m_name = name;3986m_type = Type::Item;3987if (key_name && key_name[0])3988m_key_name = key_name;3989} else {3990m_type = Type::Separator;3991}3992}39933994void Menu::RecalculateNameLengths() {3995m_max_submenu_name_length = 0;3996m_max_submenu_key_name_length = 0;3997Menus &submenus = GetSubmenus();3998const size_t num_submenus = submenus.size();3999for (size_t i = 0; i < num_submenus; ++i) {4000Menu *submenu = submenus[i].get();4001if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size())4002m_max_submenu_name_length = submenu->m_name.size();4003if (static_cast<size_t>(m_max_submenu_key_name_length) <4004submenu->m_key_name.size())4005m_max_submenu_key_name_length = submenu->m_key_name.size();4006}4007}40084009void Menu::AddSubmenu(const MenuSP &menu_sp) {4010menu_sp->m_parent = this;4011if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size())4012m_max_submenu_name_length = menu_sp->m_name.size();4013if (static_cast<size_t>(m_max_submenu_key_name_length) <4014menu_sp->m_key_name.size())4015m_max_submenu_key_name_length = menu_sp->m_key_name.size();4016m_submenus.push_back(menu_sp);4017}40184019void Menu::DrawMenuTitle(Window &window, bool highlight) {4020if (m_type == Type::Separator) {4021window.MoveCursor(0, window.GetCursorY());4022window.PutChar(ACS_LTEE);4023int width = window.GetWidth();4024if (width > 2) {4025width -= 2;4026for (int i = 0; i < width; ++i)4027window.PutChar(ACS_HLINE);4028}4029window.PutChar(ACS_RTEE);4030} else {4031const int shortcut_key = m_key_value;4032bool underlined_shortcut = false;4033const attr_t highlight_attr = A_REVERSE;4034if (highlight)4035window.AttributeOn(highlight_attr);4036if (llvm::isPrint(shortcut_key)) {4037size_t lower_pos = m_name.find(tolower(shortcut_key));4038size_t upper_pos = m_name.find(toupper(shortcut_key));4039const char *name = m_name.c_str();4040size_t pos = std::min<size_t>(lower_pos, upper_pos);4041if (pos != std::string::npos) {4042underlined_shortcut = true;4043if (pos > 0) {4044window.PutCString(name, pos);4045name += pos;4046}4047const attr_t shortcut_attr = A_UNDERLINE | A_BOLD;4048window.AttributeOn(shortcut_attr);4049window.PutChar(name[0]);4050window.AttributeOff(shortcut_attr);4051name++;4052if (name[0])4053window.PutCString(name);4054}4055}40564057if (!underlined_shortcut) {4058window.PutCString(m_name.c_str());4059}40604061if (highlight)4062window.AttributeOff(highlight_attr);40634064if (m_key_name.empty()) {4065if (!underlined_shortcut && llvm::isPrint(m_key_value)) {4066window.AttributeOn(COLOR_PAIR(MagentaOnWhite));4067window.Printf(" (%c)", m_key_value);4068window.AttributeOff(COLOR_PAIR(MagentaOnWhite));4069}4070} else {4071window.AttributeOn(COLOR_PAIR(MagentaOnWhite));4072window.Printf(" (%s)", m_key_name.c_str());4073window.AttributeOff(COLOR_PAIR(MagentaOnWhite));4074}4075}4076}40774078bool Menu::WindowDelegateDraw(Window &window, bool force) {4079Menus &submenus = GetSubmenus();4080const size_t num_submenus = submenus.size();4081const int selected_idx = GetSelectedSubmenuIndex();4082Menu::Type menu_type = GetType();4083switch (menu_type) {4084case Menu::Type::Bar: {4085window.SetBackground(BlackOnWhite);4086window.MoveCursor(0, 0);4087for (size_t i = 0; i < num_submenus; ++i) {4088Menu *menu = submenus[i].get();4089if (i > 0)4090window.PutChar(' ');4091menu->SetStartingColumn(window.GetCursorX());4092window.PutCString("| ");4093menu->DrawMenuTitle(window, false);4094}4095window.PutCString(" |");4096} break;40974098case Menu::Type::Item: {4099int y = 1;4100int x = 3;4101// Draw the menu4102int cursor_x = 0;4103int cursor_y = 0;4104window.Erase();4105window.SetBackground(BlackOnWhite);4106window.Box();4107for (size_t i = 0; i < num_submenus; ++i) {4108const bool is_selected = (i == static_cast<size_t>(selected_idx));4109window.MoveCursor(x, y + i);4110if (is_selected) {4111// Remember where we want the cursor to be4112cursor_x = x - 1;4113cursor_y = y + i;4114}4115submenus[i]->DrawMenuTitle(window, is_selected);4116}4117window.MoveCursor(cursor_x, cursor_y);4118} break;41194120default:4121case Menu::Type::Separator:4122break;4123}4124return true; // Drawing handled...4125}41264127HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) {4128HandleCharResult result = eKeyNotHandled;41294130Menus &submenus = GetSubmenus();4131const size_t num_submenus = submenus.size();4132const int selected_idx = GetSelectedSubmenuIndex();4133Menu::Type menu_type = GetType();4134if (menu_type == Menu::Type::Bar) {4135MenuSP run_menu_sp;4136switch (key) {4137case KEY_DOWN:4138case KEY_UP:4139// Show last menu or first menu4140if (selected_idx < static_cast<int>(num_submenus))4141run_menu_sp = submenus[selected_idx];4142else if (!submenus.empty())4143run_menu_sp = submenus.front();4144result = eKeyHandled;4145break;41464147case KEY_RIGHT:4148++m_selected;4149if (m_selected >= static_cast<int>(num_submenus))4150m_selected = 0;4151if (m_selected < static_cast<int>(num_submenus))4152run_menu_sp = submenus[m_selected];4153else if (!submenus.empty())4154run_menu_sp = submenus.front();4155result = eKeyHandled;4156break;41574158case KEY_LEFT:4159--m_selected;4160if (m_selected < 0)4161m_selected = num_submenus - 1;4162if (m_selected < static_cast<int>(num_submenus))4163run_menu_sp = submenus[m_selected];4164else if (!submenus.empty())4165run_menu_sp = submenus.front();4166result = eKeyHandled;4167break;41684169default:4170for (size_t i = 0; i < num_submenus; ++i) {4171if (submenus[i]->GetKeyValue() == key) {4172SetSelectedSubmenuIndex(i);4173run_menu_sp = submenus[i];4174result = eKeyHandled;4175break;4176}4177}4178break;4179}41804181if (run_menu_sp) {4182// Run the action on this menu in case we need to populate the menu with4183// dynamic content and also in case check marks, and any other menu4184// decorations need to be calculated4185if (run_menu_sp->Action() == MenuActionResult::Quit)4186return eQuitApplication;41874188Rect menu_bounds;4189menu_bounds.origin.x = run_menu_sp->GetStartingColumn();4190menu_bounds.origin.y = 1;4191menu_bounds.size.width = run_menu_sp->GetDrawWidth();4192menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2;4193if (m_menu_window_sp)4194window.GetParent()->RemoveSubWindow(m_menu_window_sp.get());41954196m_menu_window_sp = window.GetParent()->CreateSubWindow(4197run_menu_sp->GetName().c_str(), menu_bounds, true);4198m_menu_window_sp->SetDelegate(run_menu_sp);4199}4200} else if (menu_type == Menu::Type::Item) {4201switch (key) {4202case KEY_DOWN:4203if (m_submenus.size() > 1) {4204const int start_select = m_selected;4205while (++m_selected != start_select) {4206if (static_cast<size_t>(m_selected) >= num_submenus)4207m_selected = 0;4208if (m_submenus[m_selected]->GetType() == Type::Separator)4209continue;4210else4211break;4212}4213return eKeyHandled;4214}4215break;42164217case KEY_UP:4218if (m_submenus.size() > 1) {4219const int start_select = m_selected;4220while (--m_selected != start_select) {4221if (m_selected < static_cast<int>(0))4222m_selected = num_submenus - 1;4223if (m_submenus[m_selected]->GetType() == Type::Separator)4224continue;4225else4226break;4227}4228return eKeyHandled;4229}4230break;42314232case KEY_RETURN:4233if (static_cast<size_t>(selected_idx) < num_submenus) {4234if (submenus[selected_idx]->Action() == MenuActionResult::Quit)4235return eQuitApplication;4236window.GetParent()->RemoveSubWindow(&window);4237return eKeyHandled;4238}4239break;42404241case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in4242// case other chars are entered for escaped sequences4243window.GetParent()->RemoveSubWindow(&window);4244return eKeyHandled;42454246default:4247for (size_t i = 0; i < num_submenus; ++i) {4248Menu *menu = submenus[i].get();4249if (menu->GetKeyValue() == key) {4250SetSelectedSubmenuIndex(i);4251window.GetParent()->RemoveSubWindow(&window);4252if (menu->Action() == MenuActionResult::Quit)4253return eQuitApplication;4254return eKeyHandled;4255}4256}4257break;4258}4259} else if (menu_type == Menu::Type::Separator) {4260}4261return result;4262}42634264class Application {4265public:4266Application(FILE *in, FILE *out) : m_window_sp(), m_in(in), m_out(out) {}42674268~Application() {4269m_window_delegates.clear();4270m_window_sp.reset();4271if (m_screen) {4272::delscreen(m_screen);4273m_screen = nullptr;4274}4275}42764277void Initialize() {4278m_screen = ::newterm(nullptr, m_out, m_in);4279::start_color();4280::curs_set(0);4281::noecho();4282::keypad(stdscr, TRUE);4283}42844285void Terminate() { ::endwin(); }42864287void Run(Debugger &debugger) {4288bool done = false;4289int delay_in_tenths_of_a_second = 1;42904291// Alas the threading model in curses is a bit lame so we need to resort4292// to polling every 0.5 seconds. We could poll for stdin ourselves and4293// then pass the keys down but then we need to translate all of the escape4294// sequences ourselves. So we resort to polling for input because we need4295// to receive async process events while in this loop.42964297halfdelay(delay_in_tenths_of_a_second); // Poll using some number of4298// tenths of seconds seconds when4299// calling Window::GetChar()43004301ListenerSP listener_sp(4302Listener::MakeListener("lldb.IOHandler.curses.Application"));4303ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass());4304debugger.EnableForwardEvents(listener_sp);43054306m_update_screen = true;4307#if defined(__APPLE__)4308std::deque<int> escape_chars;4309#endif43104311while (!done) {4312if (m_update_screen) {4313m_window_sp->Draw(false);4314// All windows should be calling Window::DeferredRefresh() instead of4315// Window::Refresh() so we can do a single update and avoid any screen4316// blinking4317update_panels();43184319// Cursor hiding isn't working on MacOSX, so hide it in the top left4320// corner4321m_window_sp->MoveCursor(0, 0);43224323doupdate();4324m_update_screen = false;4325}43264327#if defined(__APPLE__)4328// Terminal.app doesn't map its function keys correctly, F1-F4 default4329// to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if4330// possible4331int ch;4332if (escape_chars.empty())4333ch = m_window_sp->GetChar();4334else {4335ch = escape_chars.front();4336escape_chars.pop_front();4337}4338if (ch == KEY_ESCAPE) {4339int ch2 = m_window_sp->GetChar();4340if (ch2 == 'O') {4341int ch3 = m_window_sp->GetChar();4342switch (ch3) {4343case 'P':4344ch = KEY_F(1);4345break;4346case 'Q':4347ch = KEY_F(2);4348break;4349case 'R':4350ch = KEY_F(3);4351break;4352case 'S':4353ch = KEY_F(4);4354break;4355default:4356escape_chars.push_back(ch2);4357if (ch3 != -1)4358escape_chars.push_back(ch3);4359break;4360}4361} else if (ch2 != -1)4362escape_chars.push_back(ch2);4363}4364#else4365int ch = m_window_sp->GetChar();43664367#endif4368if (ch == -1) {4369if (feof(m_in) || ferror(m_in)) {4370done = true;4371} else {4372// Just a timeout from using halfdelay(), check for events4373EventSP event_sp;4374while (listener_sp->PeekAtNextEvent()) {4375listener_sp->GetEvent(event_sp, std::chrono::seconds(0));43764377if (event_sp) {4378Broadcaster *broadcaster = event_sp->GetBroadcaster();4379if (broadcaster) {4380// uint32_t event_type = event_sp->GetType();4381ConstString broadcaster_class(4382broadcaster->GetBroadcasterClass());4383if (broadcaster_class == broadcaster_class_process) {4384m_update_screen = true;4385continue; // Don't get any key, just update our view4386}4387}4388}4389}4390}4391} else {4392HandleCharResult key_result = m_window_sp->HandleChar(ch);4393switch (key_result) {4394case eKeyHandled:4395m_update_screen = true;4396break;4397case eKeyNotHandled:4398if (ch == 12) { // Ctrl+L, force full redraw4399redrawwin(m_window_sp->get());4400m_update_screen = true;4401}4402break;4403case eQuitApplication:4404done = true;4405break;4406}4407}4408}44094410debugger.CancelForwardEvents(listener_sp);4411}44124413WindowSP &GetMainWindow() {4414if (!m_window_sp)4415m_window_sp = std::make_shared<Window>("main", stdscr, false);4416return m_window_sp;4417}44184419void TerminalSizeChanged() {4420::endwin();4421::refresh();4422Rect content_bounds = m_window_sp->GetFrame();4423m_window_sp->SetBounds(content_bounds);4424if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar"))4425menubar_window_sp->SetBounds(content_bounds.MakeMenuBar());4426if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status"))4427status_window_sp->SetBounds(content_bounds.MakeStatusBar());44284429WindowSP source_window_sp = m_window_sp->FindSubWindow("Source");4430WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables");4431WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers");4432WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads");44334434Rect threads_bounds;4435Rect source_variables_bounds;4436content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,4437threads_bounds);4438if (threads_window_sp)4439threads_window_sp->SetBounds(threads_bounds);4440else4441source_variables_bounds = content_bounds;44424443Rect source_bounds;4444Rect variables_registers_bounds;4445source_variables_bounds.HorizontalSplitPercentage(44460.70, source_bounds, variables_registers_bounds);4447if (variables_window_sp || registers_window_sp) {4448if (variables_window_sp && registers_window_sp) {4449Rect variables_bounds;4450Rect registers_bounds;4451variables_registers_bounds.VerticalSplitPercentage(44520.50, variables_bounds, registers_bounds);4453variables_window_sp->SetBounds(variables_bounds);4454registers_window_sp->SetBounds(registers_bounds);4455} else if (variables_window_sp) {4456variables_window_sp->SetBounds(variables_registers_bounds);4457} else {4458registers_window_sp->SetBounds(variables_registers_bounds);4459}4460} else {4461source_bounds = source_variables_bounds;4462}44634464source_window_sp->SetBounds(source_bounds);44654466touchwin(stdscr);4467redrawwin(m_window_sp->get());4468m_update_screen = true;4469}44704471protected:4472WindowSP m_window_sp;4473WindowDelegates m_window_delegates;4474SCREEN *m_screen = nullptr;4475FILE *m_in;4476FILE *m_out;4477bool m_update_screen = false;4478};44794480} // namespace curses44814482using namespace curses;44834484struct Row {4485ValueObjectUpdater value;4486Row *parent;4487// The process stop ID when the children were calculated.4488uint32_t children_stop_id = 0;4489int row_idx = 0;4490int x = 1;4491int y = 1;4492bool might_have_children;4493bool expanded = false;4494bool calculated_children = false;4495std::vector<Row> children;44964497Row(const ValueObjectSP &v, Row *p)4498: value(v), parent(p),4499might_have_children(v ? v->MightHaveChildren() : false) {}45004501size_t GetDepth() const {4502if (parent)4503return 1 + parent->GetDepth();4504return 0;4505}45064507void Expand() { expanded = true; }45084509std::vector<Row> &GetChildren() {4510ProcessSP process_sp = value.GetProcessSP();4511auto stop_id = process_sp->GetStopID();4512if (process_sp && stop_id != children_stop_id) {4513children_stop_id = stop_id;4514calculated_children = false;4515}4516if (!calculated_children) {4517children.clear();4518calculated_children = true;4519ValueObjectSP valobj = value.GetSP();4520if (valobj) {4521const uint32_t num_children = valobj->GetNumChildrenIgnoringErrors();4522for (size_t i = 0; i < num_children; ++i) {4523children.push_back(Row(valobj->GetChildAtIndex(i), this));4524}4525}4526}4527return children;4528}45294530void Unexpand() {4531expanded = false;4532calculated_children = false;4533children.clear();4534}45354536void DrawTree(Window &window) {4537if (parent)4538parent->DrawTreeForChild(window, this, 0);45394540if (might_have_children &&4541(!calculated_children || !GetChildren().empty())) {4542// It we can get UTF8 characters to work we should try to use the4543// "symbol" UTF8 string below4544// const char *symbol = "";4545// if (row.expanded)4546// symbol = "\xe2\x96\xbd ";4547// else4548// symbol = "\xe2\x96\xb7 ";4549// window.PutCString (symbol);45504551// The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v'4552// or '>' character...4553// if (expanded)4554// window.PutChar (ACS_DARROW);4555// else4556// window.PutChar (ACS_RARROW);4557// Since we can't find any good looking right arrow/down arrow symbols,4558// just use a diamond...4559window.PutChar(ACS_DIAMOND);4560window.PutChar(ACS_HLINE);4561}4562}45634564void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) {4565if (parent)4566parent->DrawTreeForChild(window, this, reverse_depth + 1);45674568if (&GetChildren().back() == child) {4569// Last child4570if (reverse_depth == 0) {4571window.PutChar(ACS_LLCORNER);4572window.PutChar(ACS_HLINE);4573} else {4574window.PutChar(' ');4575window.PutChar(' ');4576}4577} else {4578if (reverse_depth == 0) {4579window.PutChar(ACS_LTEE);4580window.PutChar(ACS_HLINE);4581} else {4582window.PutChar(ACS_VLINE);4583window.PutChar(' ');4584}4585}4586}4587};45884589struct DisplayOptions {4590bool show_types;4591};45924593class TreeItem;45944595class TreeDelegate {4596public:4597TreeDelegate() = default;4598virtual ~TreeDelegate() = default;45994600virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0;4601virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0;4602virtual void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,4603TreeItem *&selected_item) {}4604// This is invoked when a tree item is selected. If true is returned, the4605// views are updated.4606virtual bool TreeDelegateItemSelected(TreeItem &item) = 0;4607virtual bool TreeDelegateExpandRootByDefault() { return false; }4608// This is mostly useful for root tree delegates. If false is returned,4609// drawing will be skipped completely. This is needed, for instance, in4610// skipping drawing of the threads tree if there is no running process.4611virtual bool TreeDelegateShouldDraw() { return true; }4612};46134614typedef std::shared_ptr<TreeDelegate> TreeDelegateSP;46154616struct TreeItemData {4617TreeItemData(TreeItem *parent, TreeDelegate &delegate,4618bool might_have_children, bool is_expanded)4619: m_parent(parent), m_delegate(&delegate),4620m_might_have_children(might_have_children), m_is_expanded(is_expanded) {4621}46224623protected:4624TreeItem *m_parent;4625TreeDelegate *m_delegate;4626void *m_user_data = nullptr;4627uint64_t m_identifier = 0;4628std::string m_text;4629int m_row_idx = -1; // Zero based visible row index, -1 if not visible or for4630// the root item4631bool m_might_have_children;4632bool m_is_expanded = false;4633};46344635class TreeItem : public TreeItemData {4636public:4637TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children)4638: TreeItemData(parent, delegate, might_have_children,4639parent == nullptr4640? delegate.TreeDelegateExpandRootByDefault()4641: false),4642m_children() {}46434644TreeItem(const TreeItem &) = delete;4645TreeItem &operator=(const TreeItem &rhs) = delete;46464647TreeItem &operator=(TreeItem &&rhs) {4648if (this != &rhs) {4649TreeItemData::operator=(std::move(rhs));4650AdoptChildren(rhs.m_children);4651}4652return *this;4653}46544655TreeItem(TreeItem &&rhs) : TreeItemData(std::move(rhs)) {4656AdoptChildren(rhs.m_children);4657}46584659size_t GetDepth() const {4660if (m_parent)4661return 1 + m_parent->GetDepth();4662return 0;4663}46644665int GetRowIndex() const { return m_row_idx; }46664667void ClearChildren() { m_children.clear(); }46684669void Resize(size_t n, TreeDelegate &delegate, bool might_have_children) {4670if (m_children.size() >= n) {4671m_children.erase(m_children.begin() + n, m_children.end());4672return;4673}4674m_children.reserve(n);4675std::generate_n(std::back_inserter(m_children), n - m_children.size(),4676[&, parent = this]() {4677return TreeItem(parent, delegate, might_have_children);4678});4679}46804681TreeItem &operator[](size_t i) { return m_children[i]; }46824683void SetRowIndex(int row_idx) { m_row_idx = row_idx; }46844685size_t GetNumChildren() {4686m_delegate->TreeDelegateGenerateChildren(*this);4687return m_children.size();4688}46894690void ItemWasSelected() { m_delegate->TreeDelegateItemSelected(*this); }46914692void CalculateRowIndexes(int &row_idx) {4693SetRowIndex(row_idx);4694++row_idx;46954696const bool expanded = IsExpanded();46974698// The root item must calculate its children, or we must calculate the4699// number of children if the item is expanded4700if (m_parent == nullptr || expanded)4701GetNumChildren();47024703for (auto &item : m_children) {4704if (expanded)4705item.CalculateRowIndexes(row_idx);4706else4707item.SetRowIndex(-1);4708}4709}47104711TreeItem *GetParent() { return m_parent; }47124713bool IsExpanded() const { return m_is_expanded; }47144715void Expand() { m_is_expanded = true; }47164717void Unexpand() { m_is_expanded = false; }47184719bool Draw(Window &window, const int first_visible_row,4720const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) {4721if (num_rows_left <= 0)4722return false;47234724if (m_row_idx >= first_visible_row) {4725window.MoveCursor(2, row_idx + 1);47264727if (m_parent)4728m_parent->DrawTreeForChild(window, this, 0);47294730if (m_might_have_children) {4731// It we can get UTF8 characters to work we should try to use the4732// "symbol" UTF8 string below4733// const char *symbol = "";4734// if (row.expanded)4735// symbol = "\xe2\x96\xbd ";4736// else4737// symbol = "\xe2\x96\xb7 ";4738// window.PutCString (symbol);47394740// The ACS_DARROW and ACS_RARROW don't look very nice they are just a4741// 'v' or '>' character...4742// if (expanded)4743// window.PutChar (ACS_DARROW);4744// else4745// window.PutChar (ACS_RARROW);4746// Since we can't find any good looking right arrow/down arrow symbols,4747// just use a diamond...4748window.PutChar(ACS_DIAMOND);4749window.PutChar(ACS_HLINE);4750}4751bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) &&4752window.IsActive();47534754if (highlight)4755window.AttributeOn(A_REVERSE);47564757m_delegate->TreeDelegateDrawTreeItem(*this, window);47584759if (highlight)4760window.AttributeOff(A_REVERSE);4761++row_idx;4762--num_rows_left;4763}47644765if (num_rows_left <= 0)4766return false; // We are done drawing...47674768if (IsExpanded()) {4769for (auto &item : m_children) {4770// If we displayed all the rows and item.Draw() returns false we are4771// done drawing and can exit this for loop4772if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx,4773num_rows_left))4774break;4775}4776}4777return num_rows_left >= 0; // Return true if not done drawing yet4778}47794780void DrawTreeForChild(Window &window, TreeItem *child,4781uint32_t reverse_depth) {4782if (m_parent)4783m_parent->DrawTreeForChild(window, this, reverse_depth + 1);47844785if (&m_children.back() == child) {4786// Last child4787if (reverse_depth == 0) {4788window.PutChar(ACS_LLCORNER);4789window.PutChar(ACS_HLINE);4790} else {4791window.PutChar(' ');4792window.PutChar(' ');4793}4794} else {4795if (reverse_depth == 0) {4796window.PutChar(ACS_LTEE);4797window.PutChar(ACS_HLINE);4798} else {4799window.PutChar(ACS_VLINE);4800window.PutChar(' ');4801}4802}4803}48044805TreeItem *GetItemForRowIndex(uint32_t row_idx) {4806if (static_cast<uint32_t>(m_row_idx) == row_idx)4807return this;4808if (m_children.empty())4809return nullptr;4810if (IsExpanded()) {4811for (auto &item : m_children) {4812TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx);4813if (selected_item_ptr)4814return selected_item_ptr;4815}4816}4817return nullptr;4818}48194820void *GetUserData() const { return m_user_data; }48214822void SetUserData(void *user_data) { m_user_data = user_data; }48234824uint64_t GetIdentifier() const { return m_identifier; }48254826void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }48274828const std::string &GetText() const { return m_text; }48294830void SetText(const char *text) {4831if (text == nullptr) {4832m_text.clear();4833return;4834}4835m_text = text;4836}48374838void SetMightHaveChildren(bool b) { m_might_have_children = b; }48394840protected:4841void AdoptChildren(std::vector<TreeItem> &children) {4842m_children = std::move(children);4843for (auto &child : m_children)4844child.m_parent = this;4845}48464847std::vector<TreeItem> m_children;4848};48494850class TreeWindowDelegate : public WindowDelegate {4851public:4852TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp)4853: m_debugger(debugger), m_delegate_sp(delegate_sp),4854m_root(nullptr, *delegate_sp, true) {}48554856int NumVisibleRows() const { return m_max_y - m_min_y; }48574858bool WindowDelegateDraw(Window &window, bool force) override {4859m_min_x = 2;4860m_min_y = 1;4861m_max_x = window.GetWidth() - 1;4862m_max_y = window.GetHeight() - 1;48634864window.Erase();4865window.DrawTitleBox(window.GetName());48664867if (!m_delegate_sp->TreeDelegateShouldDraw()) {4868m_selected_item = nullptr;4869return true;4870}48714872const int num_visible_rows = NumVisibleRows();4873m_num_rows = 0;4874m_root.CalculateRowIndexes(m_num_rows);4875m_delegate_sp->TreeDelegateUpdateSelection(m_root, m_selected_row_idx,4876m_selected_item);48774878// If we unexpanded while having something selected our total number of4879// rows is less than the num visible rows, then make sure we show all the4880// rows by setting the first visible row accordingly.4881if (m_first_visible_row > 0 && m_num_rows < num_visible_rows)4882m_first_visible_row = 0;48834884// Make sure the selected row is always visible4885if (m_selected_row_idx < m_first_visible_row)4886m_first_visible_row = m_selected_row_idx;4887else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)4888m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;48894890int row_idx = 0;4891int num_rows_left = num_visible_rows;4892m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx,4893num_rows_left);4894// Get the selected row4895m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);48964897return true; // Drawing handled4898}48994900const char *WindowDelegateGetHelpText() override {4901return "Thread window keyboard shortcuts:";4902}49034904KeyHelp *WindowDelegateGetKeyHelp() override {4905static curses::KeyHelp g_source_view_key_help[] = {4906{KEY_UP, "Select previous item"},4907{KEY_DOWN, "Select next item"},4908{KEY_RIGHT, "Expand the selected item"},4909{KEY_LEFT,4910"Unexpand the selected item or select parent if not expanded"},4911{KEY_PPAGE, "Page up"},4912{KEY_NPAGE, "Page down"},4913{'h', "Show help dialog"},4914{' ', "Toggle item expansion"},4915{',', "Page up"},4916{'.', "Page down"},4917{'\0', nullptr}};4918return g_source_view_key_help;4919}49204921HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {4922switch (c) {4923case ',':4924case KEY_PPAGE:4925// Page up key4926if (m_first_visible_row > 0) {4927if (m_first_visible_row > m_max_y)4928m_first_visible_row -= m_max_y;4929else4930m_first_visible_row = 0;4931m_selected_row_idx = m_first_visible_row;4932m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);4933if (m_selected_item)4934m_selected_item->ItemWasSelected();4935}4936return eKeyHandled;49374938case '.':4939case KEY_NPAGE:4940// Page down key4941if (m_num_rows > m_max_y) {4942if (m_first_visible_row + m_max_y < m_num_rows) {4943m_first_visible_row += m_max_y;4944m_selected_row_idx = m_first_visible_row;4945m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);4946if (m_selected_item)4947m_selected_item->ItemWasSelected();4948}4949}4950return eKeyHandled;49514952case KEY_UP:4953if (m_selected_row_idx > 0) {4954--m_selected_row_idx;4955m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);4956if (m_selected_item)4957m_selected_item->ItemWasSelected();4958}4959return eKeyHandled;49604961case KEY_DOWN:4962if (m_selected_row_idx + 1 < m_num_rows) {4963++m_selected_row_idx;4964m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);4965if (m_selected_item)4966m_selected_item->ItemWasSelected();4967}4968return eKeyHandled;49694970case KEY_RIGHT:4971if (m_selected_item) {4972if (!m_selected_item->IsExpanded())4973m_selected_item->Expand();4974}4975return eKeyHandled;49764977case KEY_LEFT:4978if (m_selected_item) {4979if (m_selected_item->IsExpanded())4980m_selected_item->Unexpand();4981else if (m_selected_item->GetParent()) {4982m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex();4983m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);4984if (m_selected_item)4985m_selected_item->ItemWasSelected();4986}4987}4988return eKeyHandled;49894990case ' ':4991// Toggle expansion state when SPACE is pressed4992if (m_selected_item) {4993if (m_selected_item->IsExpanded())4994m_selected_item->Unexpand();4995else4996m_selected_item->Expand();4997}4998return eKeyHandled;49995000case 'h':5001window.CreateHelpSubwindow();5002return eKeyHandled;50035004default:5005break;5006}5007return eKeyNotHandled;5008}50095010protected:5011Debugger &m_debugger;5012TreeDelegateSP m_delegate_sp;5013TreeItem m_root;5014TreeItem *m_selected_item = nullptr;5015int m_num_rows = 0;5016int m_selected_row_idx = 0;5017int m_first_visible_row = 0;5018int m_min_x = 0;5019int m_min_y = 0;5020int m_max_x = 0;5021int m_max_y = 0;5022};50235024// A tree delegate that just draws the text member of the tree item, it doesn't5025// have any children or actions.5026class TextTreeDelegate : public TreeDelegate {5027public:5028TextTreeDelegate() : TreeDelegate() {}50295030~TextTreeDelegate() override = default;50315032void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {5033window.PutCStringTruncated(1, item.GetText().c_str());5034}50355036void TreeDelegateGenerateChildren(TreeItem &item) override {}50375038bool TreeDelegateItemSelected(TreeItem &item) override { return false; }5039};50405041class FrameTreeDelegate : public TreeDelegate {5042public:5043FrameTreeDelegate() : TreeDelegate() {5044FormatEntity::Parse(5045"#${frame.index}: {${function.name}${function.pc-offset}}}", m_format);5046}50475048~FrameTreeDelegate() override = default;50495050void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {5051Thread *thread = (Thread *)item.GetUserData();5052if (thread) {5053const uint64_t frame_idx = item.GetIdentifier();5054StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx);5055if (frame_sp) {5056StreamString strm;5057const SymbolContext &sc =5058frame_sp->GetSymbolContext(eSymbolContextEverything);5059ExecutionContext exe_ctx(frame_sp);5060if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr,5061nullptr, false, false)) {5062int right_pad = 1;5063window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());5064}5065}5066}5067}50685069void TreeDelegateGenerateChildren(TreeItem &item) override {5070// No children for frames yet...5071}50725073bool TreeDelegateItemSelected(TreeItem &item) override {5074Thread *thread = (Thread *)item.GetUserData();5075if (thread) {5076thread->GetProcess()->GetThreadList().SetSelectedThreadByID(5077thread->GetID());5078const uint64_t frame_idx = item.GetIdentifier();5079thread->SetSelectedFrameByIndex(frame_idx);5080return true;5081}5082return false;5083}50845085protected:5086FormatEntity::Entry m_format;5087};50885089class ThreadTreeDelegate : public TreeDelegate {5090public:5091ThreadTreeDelegate(Debugger &debugger)5092: TreeDelegate(), m_debugger(debugger) {5093FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop "5094"reason = ${thread.stop-reason}}",5095m_format);5096}50975098~ThreadTreeDelegate() override = default;50995100ProcessSP GetProcess() {5101return m_debugger.GetCommandInterpreter()5102.GetExecutionContext()5103.GetProcessSP();5104}51055106ThreadSP GetThread(const TreeItem &item) {5107ProcessSP process_sp = GetProcess();5108if (process_sp)5109return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier());5110return ThreadSP();5111}51125113void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {5114ThreadSP thread_sp = GetThread(item);5115if (thread_sp) {5116StreamString strm;5117ExecutionContext exe_ctx(thread_sp);5118if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,5119nullptr, false, false)) {5120int right_pad = 1;5121window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());5122}5123}5124}51255126void TreeDelegateGenerateChildren(TreeItem &item) override {5127ProcessSP process_sp = GetProcess();5128if (process_sp && process_sp->IsAlive()) {5129StateType state = process_sp->GetState();5130if (StateIsStoppedState(state, true)) {5131ThreadSP thread_sp = GetThread(item);5132if (thread_sp) {5133if (m_stop_id == process_sp->GetStopID() &&5134thread_sp->GetID() == m_tid)5135return; // Children are already up to date5136if (!m_frame_delegate_sp) {5137// Always expand the thread item the first time we show it5138m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>();5139}51405141m_stop_id = process_sp->GetStopID();5142m_tid = thread_sp->GetID();51435144size_t num_frames = thread_sp->GetStackFrameCount();5145item.Resize(num_frames, *m_frame_delegate_sp, false);5146for (size_t i = 0; i < num_frames; ++i) {5147item[i].SetUserData(thread_sp.get());5148item[i].SetIdentifier(i);5149}5150}5151return;5152}5153}5154item.ClearChildren();5155}51565157bool TreeDelegateItemSelected(TreeItem &item) override {5158ProcessSP process_sp = GetProcess();5159if (process_sp && process_sp->IsAlive()) {5160StateType state = process_sp->GetState();5161if (StateIsStoppedState(state, true)) {5162ThreadSP thread_sp = GetThread(item);5163if (thread_sp) {5164ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList();5165std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex());5166ThreadSP selected_thread_sp = thread_list.GetSelectedThread();5167if (selected_thread_sp->GetID() != thread_sp->GetID()) {5168thread_list.SetSelectedThreadByID(thread_sp->GetID());5169return true;5170}5171}5172}5173}5174return false;5175}51765177protected:5178Debugger &m_debugger;5179std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp;5180lldb::user_id_t m_tid = LLDB_INVALID_THREAD_ID;5181uint32_t m_stop_id = UINT32_MAX;5182FormatEntity::Entry m_format;5183};51845185class ThreadsTreeDelegate : public TreeDelegate {5186public:5187ThreadsTreeDelegate(Debugger &debugger)5188: TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger) {5189FormatEntity::Parse("process ${process.id}{, name = ${process.name}}",5190m_format);5191}51925193~ThreadsTreeDelegate() override = default;51945195ProcessSP GetProcess() {5196return m_debugger.GetCommandInterpreter()5197.GetExecutionContext()5198.GetProcessSP();5199}52005201bool TreeDelegateShouldDraw() override {5202ProcessSP process = GetProcess();5203if (!process)5204return false;52055206if (StateIsRunningState(process->GetState()))5207return false;52085209return true;5210}52115212void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {5213ProcessSP process_sp = GetProcess();5214if (process_sp && process_sp->IsAlive()) {5215StreamString strm;5216ExecutionContext exe_ctx(process_sp);5217if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,5218nullptr, false, false)) {5219int right_pad = 1;5220window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());5221}5222}5223}52245225void TreeDelegateGenerateChildren(TreeItem &item) override {5226ProcessSP process_sp = GetProcess();5227m_update_selection = false;5228if (process_sp && process_sp->IsAlive()) {5229StateType state = process_sp->GetState();5230if (StateIsStoppedState(state, true)) {5231const uint32_t stop_id = process_sp->GetStopID();5232if (m_stop_id == stop_id)5233return; // Children are already up to date52345235m_stop_id = stop_id;5236m_update_selection = true;52375238if (!m_thread_delegate_sp) {5239// Always expand the thread item the first time we show it5240// item.Expand();5241m_thread_delegate_sp =5242std::make_shared<ThreadTreeDelegate>(m_debugger);5243}52445245ThreadList &threads = process_sp->GetThreadList();5246std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());5247ThreadSP selected_thread = threads.GetSelectedThread();5248size_t num_threads = threads.GetSize();5249item.Resize(num_threads, *m_thread_delegate_sp, false);5250for (size_t i = 0; i < num_threads; ++i) {5251ThreadSP thread = threads.GetThreadAtIndex(i);5252item[i].SetIdentifier(thread->GetID());5253item[i].SetMightHaveChildren(true);5254if (selected_thread->GetID() == thread->GetID())5255item[i].Expand();5256}5257return;5258}5259}5260item.ClearChildren();5261}52625263void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,5264TreeItem *&selected_item) override {5265if (!m_update_selection)5266return;52675268ProcessSP process_sp = GetProcess();5269if (!(process_sp && process_sp->IsAlive()))5270return;52715272StateType state = process_sp->GetState();5273if (!StateIsStoppedState(state, true))5274return;52755276ThreadList &threads = process_sp->GetThreadList();5277std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());5278ThreadSP selected_thread = threads.GetSelectedThread();5279size_t num_threads = threads.GetSize();5280for (size_t i = 0; i < num_threads; ++i) {5281ThreadSP thread = threads.GetThreadAtIndex(i);5282if (selected_thread->GetID() == thread->GetID()) {5283selected_item =5284&root[i][thread->GetSelectedFrameIndex(SelectMostRelevantFrame)];5285selection_index = selected_item->GetRowIndex();5286return;5287}5288}5289}52905291bool TreeDelegateItemSelected(TreeItem &item) override { return false; }52925293bool TreeDelegateExpandRootByDefault() override { return true; }52945295protected:5296std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp;5297Debugger &m_debugger;5298uint32_t m_stop_id = UINT32_MAX;5299bool m_update_selection = false;5300FormatEntity::Entry m_format;5301};53025303class BreakpointLocationTreeDelegate : public TreeDelegate {5304public:5305BreakpointLocationTreeDelegate(Debugger &debugger)5306: TreeDelegate(), m_debugger(debugger) {}53075308~BreakpointLocationTreeDelegate() override = default;53095310Process *GetProcess() {5311ExecutionContext exe_ctx(5312m_debugger.GetCommandInterpreter().GetExecutionContext());5313return exe_ctx.GetProcessPtr();5314}53155316BreakpointLocationSP GetBreakpointLocation(const TreeItem &item) {5317Breakpoint *breakpoint = (Breakpoint *)item.GetUserData();5318return breakpoint->GetLocationAtIndex(item.GetIdentifier());5319}53205321void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {5322BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item);5323Process *process = GetProcess();5324StreamString stream;5325stream.Printf("%i.%i: ", breakpoint_location->GetBreakpoint().GetID(),5326breakpoint_location->GetID());5327Address address = breakpoint_location->GetAddress();5328address.Dump(&stream, process, Address::DumpStyleResolvedDescription,5329Address::DumpStyleInvalid);5330window.PutCStringTruncated(1, stream.GetString().str().c_str());5331}53325333StringList ComputeDetailsList(BreakpointLocationSP breakpoint_location) {5334StringList details;53355336Address address = breakpoint_location->GetAddress();5337SymbolContext symbol_context;5338address.CalculateSymbolContext(&symbol_context);53395340if (symbol_context.module_sp) {5341StreamString module_stream;5342module_stream.PutCString("module = ");5343symbol_context.module_sp->GetFileSpec().Dump(5344module_stream.AsRawOstream());5345details.AppendString(module_stream.GetString());5346}53475348if (symbol_context.comp_unit != nullptr) {5349StreamString compile_unit_stream;5350compile_unit_stream.PutCString("compile unit = ");5351symbol_context.comp_unit->GetPrimaryFile().GetFilename().Dump(5352&compile_unit_stream);5353details.AppendString(compile_unit_stream.GetString());53545355if (symbol_context.function != nullptr) {5356StreamString function_stream;5357function_stream.PutCString("function = ");5358function_stream.PutCString(5359symbol_context.function->GetName().AsCString("<unknown>"));5360details.AppendString(function_stream.GetString());5361}53625363if (symbol_context.line_entry.line > 0) {5364StreamString location_stream;5365location_stream.PutCString("location = ");5366symbol_context.line_entry.DumpStopContext(&location_stream, true);5367details.AppendString(location_stream.GetString());5368}53695370} else {5371if (symbol_context.symbol) {5372StreamString symbol_stream;5373if (breakpoint_location->IsReExported())5374symbol_stream.PutCString("re-exported target = ");5375else5376symbol_stream.PutCString("symbol = ");5377symbol_stream.PutCString(5378symbol_context.symbol->GetName().AsCString("<unknown>"));5379details.AppendString(symbol_stream.GetString());5380}5381}53825383Process *process = GetProcess();53845385StreamString address_stream;5386address.Dump(&address_stream, process, Address::DumpStyleLoadAddress,5387Address::DumpStyleModuleWithFileAddress);5388details.AppendString(address_stream.GetString());53895390BreakpointSiteSP breakpoint_site = breakpoint_location->GetBreakpointSite();5391if (breakpoint_location->IsIndirect() && breakpoint_site) {5392Address resolved_address;5393resolved_address.SetLoadAddress(breakpoint_site->GetLoadAddress(),5394&breakpoint_location->GetTarget());5395Symbol *resolved_symbol = resolved_address.CalculateSymbolContextSymbol();5396if (resolved_symbol) {5397StreamString indirect_target_stream;5398indirect_target_stream.PutCString("indirect target = ");5399indirect_target_stream.PutCString(5400resolved_symbol->GetName().GetCString());5401details.AppendString(indirect_target_stream.GetString());5402}5403}54045405bool is_resolved = breakpoint_location->IsResolved();5406StreamString resolved_stream;5407resolved_stream.Printf("resolved = %s", is_resolved ? "true" : "false");5408details.AppendString(resolved_stream.GetString());54095410bool is_hardware = is_resolved && breakpoint_site->IsHardware();5411StreamString hardware_stream;5412hardware_stream.Printf("hardware = %s", is_hardware ? "true" : "false");5413details.AppendString(hardware_stream.GetString());54145415StreamString hit_count_stream;5416hit_count_stream.Printf("hit count = %-4u",5417breakpoint_location->GetHitCount());5418details.AppendString(hit_count_stream.GetString());54195420return details;5421}54225423void TreeDelegateGenerateChildren(TreeItem &item) override {5424BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item);5425StringList details = ComputeDetailsList(breakpoint_location);54265427if (!m_string_delegate_sp)5428m_string_delegate_sp = std::make_shared<TextTreeDelegate>();54295430item.Resize(details.GetSize(), *m_string_delegate_sp, false);5431for (size_t i = 0; i < details.GetSize(); i++) {5432item[i].SetText(details.GetStringAtIndex(i));5433}5434}54355436bool TreeDelegateItemSelected(TreeItem &item) override { return false; }54375438protected:5439Debugger &m_debugger;5440std::shared_ptr<TextTreeDelegate> m_string_delegate_sp;5441};54425443class BreakpointTreeDelegate : public TreeDelegate {5444public:5445BreakpointTreeDelegate(Debugger &debugger)5446: TreeDelegate(), m_debugger(debugger),5447m_breakpoint_location_delegate_sp() {}54485449~BreakpointTreeDelegate() override = default;54505451BreakpointSP GetBreakpoint(const TreeItem &item) {5452TargetSP target = m_debugger.GetSelectedTarget();5453BreakpointList &breakpoints = target->GetBreakpointList(false);5454return breakpoints.GetBreakpointAtIndex(item.GetIdentifier());5455}54565457void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {5458BreakpointSP breakpoint = GetBreakpoint(item);5459StreamString stream;5460stream.Format("{0}: ", breakpoint->GetID());5461breakpoint->GetResolverDescription(&stream);5462breakpoint->GetFilterDescription(&stream);5463window.PutCStringTruncated(1, stream.GetString().str().c_str());5464}54655466void TreeDelegateGenerateChildren(TreeItem &item) override {5467BreakpointSP breakpoint = GetBreakpoint(item);54685469if (!m_breakpoint_location_delegate_sp)5470m_breakpoint_location_delegate_sp =5471std::make_shared<BreakpointLocationTreeDelegate>(m_debugger);54725473item.Resize(breakpoint->GetNumLocations(),5474*m_breakpoint_location_delegate_sp, true);5475for (size_t i = 0; i < breakpoint->GetNumLocations(); i++) {5476item[i].SetIdentifier(i);5477item[i].SetUserData(breakpoint.get());5478}5479}54805481bool TreeDelegateItemSelected(TreeItem &item) override { return false; }54825483protected:5484Debugger &m_debugger;5485std::shared_ptr<BreakpointLocationTreeDelegate>5486m_breakpoint_location_delegate_sp;5487};54885489class BreakpointsTreeDelegate : public TreeDelegate {5490public:5491BreakpointsTreeDelegate(Debugger &debugger)5492: TreeDelegate(), m_debugger(debugger), m_breakpoint_delegate_sp() {}54935494~BreakpointsTreeDelegate() override = default;54955496bool TreeDelegateShouldDraw() override {5497TargetSP target = m_debugger.GetSelectedTarget();5498if (!target)5499return false;55005501return true;5502}55035504void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {5505window.PutCString("Breakpoints");5506}55075508void TreeDelegateGenerateChildren(TreeItem &item) override {5509TargetSP target = m_debugger.GetSelectedTarget();55105511BreakpointList &breakpoints = target->GetBreakpointList(false);5512std::unique_lock<std::recursive_mutex> lock;5513breakpoints.GetListMutex(lock);55145515if (!m_breakpoint_delegate_sp)5516m_breakpoint_delegate_sp =5517std::make_shared<BreakpointTreeDelegate>(m_debugger);55185519item.Resize(breakpoints.GetSize(), *m_breakpoint_delegate_sp, true);5520for (size_t i = 0; i < breakpoints.GetSize(); i++) {5521item[i].SetIdentifier(i);5522}5523}55245525bool TreeDelegateItemSelected(TreeItem &item) override { return false; }55265527bool TreeDelegateExpandRootByDefault() override { return true; }55285529protected:5530Debugger &m_debugger;5531std::shared_ptr<BreakpointTreeDelegate> m_breakpoint_delegate_sp;5532};55335534class ValueObjectListDelegate : public WindowDelegate {5535public:5536ValueObjectListDelegate() : m_rows() {}55375538ValueObjectListDelegate(ValueObjectList &valobj_list) : m_rows() {5539SetValues(valobj_list);5540}55415542~ValueObjectListDelegate() override = default;55435544void SetValues(ValueObjectList &valobj_list) {5545m_selected_row = nullptr;5546m_selected_row_idx = 0;5547m_first_visible_row = 0;5548m_num_rows = 0;5549m_rows.clear();5550for (auto &valobj_sp : valobj_list.GetObjects())5551m_rows.push_back(Row(valobj_sp, nullptr));5552}55535554bool WindowDelegateDraw(Window &window, bool force) override {5555m_num_rows = 0;5556m_min_x = 2;5557m_min_y = 1;5558m_max_x = window.GetWidth() - 1;5559m_max_y = window.GetHeight() - 1;55605561window.Erase();5562window.DrawTitleBox(window.GetName());55635564const int num_visible_rows = NumVisibleRows();5565const int num_rows = CalculateTotalNumberRows(m_rows);55665567// If we unexpanded while having something selected our total number of5568// rows is less than the num visible rows, then make sure we show all the5569// rows by setting the first visible row accordingly.5570if (m_first_visible_row > 0 && num_rows < num_visible_rows)5571m_first_visible_row = 0;55725573// Make sure the selected row is always visible5574if (m_selected_row_idx < m_first_visible_row)5575m_first_visible_row = m_selected_row_idx;5576else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)5577m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;55785579DisplayRows(window, m_rows, g_options);55805581// Get the selected row5582m_selected_row = GetRowForRowIndex(m_selected_row_idx);5583// Keep the cursor on the selected row so the highlight and the cursor are5584// always on the same line5585if (m_selected_row)5586window.MoveCursor(m_selected_row->x, m_selected_row->y);55875588return true; // Drawing handled5589}55905591KeyHelp *WindowDelegateGetKeyHelp() override {5592static curses::KeyHelp g_source_view_key_help[] = {5593{KEY_UP, "Select previous item"},5594{KEY_DOWN, "Select next item"},5595{KEY_RIGHT, "Expand selected item"},5596{KEY_LEFT, "Unexpand selected item or select parent if not expanded"},5597{KEY_PPAGE, "Page up"},5598{KEY_NPAGE, "Page down"},5599{'A', "Format as annotated address"},5600{'b', "Format as binary"},5601{'B', "Format as hex bytes with ASCII"},5602{'c', "Format as character"},5603{'d', "Format as a signed integer"},5604{'D', "Format selected value using the default format for the type"},5605{'f', "Format as float"},5606{'h', "Show help dialog"},5607{'i', "Format as instructions"},5608{'o', "Format as octal"},5609{'p', "Format as pointer"},5610{'s', "Format as C string"},5611{'t', "Toggle showing/hiding type names"},5612{'u', "Format as an unsigned integer"},5613{'x', "Format as hex"},5614{'X', "Format as uppercase hex"},5615{' ', "Toggle item expansion"},5616{',', "Page up"},5617{'.', "Page down"},5618{'\0', nullptr}};5619return g_source_view_key_help;5620}56215622HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {5623switch (c) {5624case 'x':5625case 'X':5626case 'o':5627case 's':5628case 'u':5629case 'd':5630case 'D':5631case 'i':5632case 'A':5633case 'p':5634case 'c':5635case 'b':5636case 'B':5637case 'f':5638// Change the format for the currently selected item5639if (m_selected_row) {5640auto valobj_sp = m_selected_row->value.GetSP();5641if (valobj_sp)5642valobj_sp->SetFormat(FormatForChar(c));5643}5644return eKeyHandled;56455646case 't':5647// Toggle showing type names5648g_options.show_types = !g_options.show_types;5649return eKeyHandled;56505651case ',':5652case KEY_PPAGE:5653// Page up key5654if (m_first_visible_row > 0) {5655if (static_cast<int>(m_first_visible_row) > m_max_y)5656m_first_visible_row -= m_max_y;5657else5658m_first_visible_row = 0;5659m_selected_row_idx = m_first_visible_row;5660}5661return eKeyHandled;56625663case '.':5664case KEY_NPAGE:5665// Page down key5666if (m_num_rows > static_cast<size_t>(m_max_y)) {5667if (m_first_visible_row + m_max_y < m_num_rows) {5668m_first_visible_row += m_max_y;5669m_selected_row_idx = m_first_visible_row;5670}5671}5672return eKeyHandled;56735674case KEY_UP:5675if (m_selected_row_idx > 0)5676--m_selected_row_idx;5677return eKeyHandled;56785679case KEY_DOWN:5680if (m_selected_row_idx + 1 < m_num_rows)5681++m_selected_row_idx;5682return eKeyHandled;56835684case KEY_RIGHT:5685if (m_selected_row) {5686if (!m_selected_row->expanded)5687m_selected_row->Expand();5688}5689return eKeyHandled;56905691case KEY_LEFT:5692if (m_selected_row) {5693if (m_selected_row->expanded)5694m_selected_row->Unexpand();5695else if (m_selected_row->parent)5696m_selected_row_idx = m_selected_row->parent->row_idx;5697}5698return eKeyHandled;56995700case ' ':5701// Toggle expansion state when SPACE is pressed5702if (m_selected_row) {5703if (m_selected_row->expanded)5704m_selected_row->Unexpand();5705else5706m_selected_row->Expand();5707}5708return eKeyHandled;57095710case 'h':5711window.CreateHelpSubwindow();5712return eKeyHandled;57135714default:5715break;5716}5717return eKeyNotHandled;5718}57195720protected:5721std::vector<Row> m_rows;5722Row *m_selected_row = nullptr;5723uint32_t m_selected_row_idx = 0;5724uint32_t m_first_visible_row = 0;5725uint32_t m_num_rows = 0;5726int m_min_x = 0;5727int m_min_y = 0;5728int m_max_x = 0;5729int m_max_y = 0;57305731static Format FormatForChar(int c) {5732switch (c) {5733case 'x':5734return eFormatHex;5735case 'X':5736return eFormatHexUppercase;5737case 'o':5738return eFormatOctal;5739case 's':5740return eFormatCString;5741case 'u':5742return eFormatUnsigned;5743case 'd':5744return eFormatDecimal;5745case 'D':5746return eFormatDefault;5747case 'i':5748return eFormatInstruction;5749case 'A':5750return eFormatAddressInfo;5751case 'p':5752return eFormatPointer;5753case 'c':5754return eFormatChar;5755case 'b':5756return eFormatBinary;5757case 'B':5758return eFormatBytesWithASCII;5759case 'f':5760return eFormatFloat;5761}5762return eFormatDefault;5763}57645765bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options,5766bool highlight, bool last_child) {5767ValueObject *valobj = row.value.GetSP().get();57685769if (valobj == nullptr)5770return false;57715772const char *type_name =5773options.show_types ? valobj->GetTypeName().GetCString() : nullptr;5774const char *name = valobj->GetName().GetCString();5775const char *value = valobj->GetValueAsCString();5776const char *summary = valobj->GetSummaryAsCString();57775778window.MoveCursor(row.x, row.y);57795780row.DrawTree(window);57815782if (highlight)5783window.AttributeOn(A_REVERSE);57845785if (type_name && type_name[0])5786window.PrintfTruncated(1, "(%s) ", type_name);57875788if (name && name[0])5789window.PutCStringTruncated(1, name);57905791attr_t changd_attr = 0;5792if (valobj->GetValueDidChange())5793changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD;57945795if (value && value[0]) {5796window.PutCStringTruncated(1, " = ");5797if (changd_attr)5798window.AttributeOn(changd_attr);5799window.PutCStringTruncated(1, value);5800if (changd_attr)5801window.AttributeOff(changd_attr);5802}58035804if (summary && summary[0]) {5805window.PutCStringTruncated(1, " ");5806if (changd_attr)5807window.AttributeOn(changd_attr);5808window.PutCStringTruncated(1, summary);5809if (changd_attr)5810window.AttributeOff(changd_attr);5811}58125813if (highlight)5814window.AttributeOff(A_REVERSE);58155816return true;5817}58185819void DisplayRows(Window &window, std::vector<Row> &rows,5820DisplayOptions &options) {5821// > 0x25B75822// \/ 0x25BD58235824bool window_is_active = window.IsActive();5825for (auto &row : rows) {5826const bool last_child = row.parent && &rows[rows.size() - 1] == &row;5827// Save the row index in each Row structure5828row.row_idx = m_num_rows;5829if ((m_num_rows >= m_first_visible_row) &&5830((m_num_rows - m_first_visible_row) <5831static_cast<size_t>(NumVisibleRows()))) {5832row.x = m_min_x;5833row.y = m_num_rows - m_first_visible_row + 1;5834if (DisplayRowObject(window, row, options,5835window_is_active &&5836m_num_rows == m_selected_row_idx,5837last_child)) {5838++m_num_rows;5839} else {5840row.x = 0;5841row.y = 0;5842}5843} else {5844row.x = 0;5845row.y = 0;5846++m_num_rows;5847}58485849if (row.expanded) {5850auto &children = row.GetChildren();5851if (!children.empty()) {5852DisplayRows(window, children, options);5853}5854}5855}5856}58575858int CalculateTotalNumberRows(std::vector<Row> &rows) {5859int row_count = 0;5860for (auto &row : rows) {5861++row_count;5862if (row.expanded)5863row_count += CalculateTotalNumberRows(row.GetChildren());5864}5865return row_count;5866}58675868static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) {5869for (auto &row : rows) {5870if (row_index == 0)5871return &row;5872else {5873--row_index;5874if (row.expanded) {5875auto &children = row.GetChildren();5876if (!children.empty()) {5877Row *result = GetRowForRowIndexImpl(children, row_index);5878if (result)5879return result;5880}5881}5882}5883}5884return nullptr;5885}58865887Row *GetRowForRowIndex(size_t row_index) {5888return GetRowForRowIndexImpl(m_rows, row_index);5889}58905891int NumVisibleRows() const { return m_max_y - m_min_y; }58925893static DisplayOptions g_options;5894};58955896class FrameVariablesWindowDelegate : public ValueObjectListDelegate {5897public:5898FrameVariablesWindowDelegate(Debugger &debugger)5899: ValueObjectListDelegate(), m_debugger(debugger) {}59005901~FrameVariablesWindowDelegate() override = default;59025903const char *WindowDelegateGetHelpText() override {5904return "Frame variable window keyboard shortcuts:";5905}59065907bool WindowDelegateDraw(Window &window, bool force) override {5908ExecutionContext exe_ctx(5909m_debugger.GetCommandInterpreter().GetExecutionContext());5910Process *process = exe_ctx.GetProcessPtr();5911Block *frame_block = nullptr;5912StackFrame *frame = nullptr;59135914if (process) {5915StateType state = process->GetState();5916if (StateIsStoppedState(state, true)) {5917frame = exe_ctx.GetFramePtr();5918if (frame)5919frame_block = frame->GetFrameBlock();5920} else if (StateIsRunningState(state)) {5921return true; // Don't do any updating when we are running5922}5923}59245925ValueObjectList local_values;5926if (frame_block) {5927// Only update the variables if they have changed5928if (m_frame_block != frame_block) {5929m_frame_block = frame_block;59305931VariableList *locals = frame->GetVariableList(true, nullptr);5932if (locals) {5933const DynamicValueType use_dynamic = eDynamicDontRunTarget;5934for (const VariableSP &local_sp : *locals) {5935ValueObjectSP value_sp =5936frame->GetValueObjectForFrameVariable(local_sp, use_dynamic);5937if (value_sp) {5938ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue();5939if (synthetic_value_sp)5940local_values.Append(synthetic_value_sp);5941else5942local_values.Append(value_sp);5943}5944}5945// Update the values5946SetValues(local_values);5947}5948}5949} else {5950m_frame_block = nullptr;5951// Update the values with an empty list if there is no frame5952SetValues(local_values);5953}59545955return ValueObjectListDelegate::WindowDelegateDraw(window, force);5956}59575958protected:5959Debugger &m_debugger;5960Block *m_frame_block = nullptr;5961};59625963class RegistersWindowDelegate : public ValueObjectListDelegate {5964public:5965RegistersWindowDelegate(Debugger &debugger)5966: ValueObjectListDelegate(), m_debugger(debugger) {}59675968~RegistersWindowDelegate() override = default;59695970const char *WindowDelegateGetHelpText() override {5971return "Register window keyboard shortcuts:";5972}59735974bool WindowDelegateDraw(Window &window, bool force) override {5975ExecutionContext exe_ctx(5976m_debugger.GetCommandInterpreter().GetExecutionContext());5977StackFrame *frame = exe_ctx.GetFramePtr();59785979ValueObjectList value_list;5980if (frame) {5981if (frame->GetStackID() != m_stack_id) {5982m_stack_id = frame->GetStackID();5983RegisterContextSP reg_ctx(frame->GetRegisterContext());5984if (reg_ctx) {5985const uint32_t num_sets = reg_ctx->GetRegisterSetCount();5986for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) {5987value_list.Append(5988ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx));5989}5990}5991SetValues(value_list);5992}5993} else {5994Process *process = exe_ctx.GetProcessPtr();5995if (process && process->IsAlive())5996return true; // Don't do any updating if we are running5997else {5998// Update the values with an empty list if there is no process or the5999// process isn't alive anymore6000SetValues(value_list);6001}6002}6003return ValueObjectListDelegate::WindowDelegateDraw(window, force);6004}60056006protected:6007Debugger &m_debugger;6008StackID m_stack_id;6009};60106011static const char *CursesKeyToCString(int ch) {6012static char g_desc[32];6013if (ch >= KEY_F0 && ch < KEY_F0 + 64) {6014snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0);6015return g_desc;6016}6017switch (ch) {6018case KEY_DOWN:6019return "down";6020case KEY_UP:6021return "up";6022case KEY_LEFT:6023return "left";6024case KEY_RIGHT:6025return "right";6026case KEY_HOME:6027return "home";6028case KEY_BACKSPACE:6029return "backspace";6030case KEY_DL:6031return "delete-line";6032case KEY_IL:6033return "insert-line";6034case KEY_DC:6035return "delete-char";6036case KEY_IC:6037return "insert-char";6038case KEY_CLEAR:6039return "clear";6040case KEY_EOS:6041return "clear-to-eos";6042case KEY_EOL:6043return "clear-to-eol";6044case KEY_SF:6045return "scroll-forward";6046case KEY_SR:6047return "scroll-backward";6048case KEY_NPAGE:6049return "page-down";6050case KEY_PPAGE:6051return "page-up";6052case KEY_STAB:6053return "set-tab";6054case KEY_CTAB:6055return "clear-tab";6056case KEY_CATAB:6057return "clear-all-tabs";6058case KEY_ENTER:6059return "enter";6060case KEY_PRINT:6061return "print";6062case KEY_LL:6063return "lower-left key";6064case KEY_A1:6065return "upper left of keypad";6066case KEY_A3:6067return "upper right of keypad";6068case KEY_B2:6069return "center of keypad";6070case KEY_C1:6071return "lower left of keypad";6072case KEY_C3:6073return "lower right of keypad";6074case KEY_BTAB:6075return "back-tab key";6076case KEY_BEG:6077return "begin key";6078case KEY_CANCEL:6079return "cancel key";6080case KEY_CLOSE:6081return "close key";6082case KEY_COMMAND:6083return "command key";6084case KEY_COPY:6085return "copy key";6086case KEY_CREATE:6087return "create key";6088case KEY_END:6089return "end key";6090case KEY_EXIT:6091return "exit key";6092case KEY_FIND:6093return "find key";6094case KEY_HELP:6095return "help key";6096case KEY_MARK:6097return "mark key";6098case KEY_MESSAGE:6099return "message key";6100case KEY_MOVE:6101return "move key";6102case KEY_NEXT:6103return "next key";6104case KEY_OPEN:6105return "open key";6106case KEY_OPTIONS:6107return "options key";6108case KEY_PREVIOUS:6109return "previous key";6110case KEY_REDO:6111return "redo key";6112case KEY_REFERENCE:6113return "reference key";6114case KEY_REFRESH:6115return "refresh key";6116case KEY_REPLACE:6117return "replace key";6118case KEY_RESTART:6119return "restart key";6120case KEY_RESUME:6121return "resume key";6122case KEY_SAVE:6123return "save key";6124case KEY_SBEG:6125return "shifted begin key";6126case KEY_SCANCEL:6127return "shifted cancel key";6128case KEY_SCOMMAND:6129return "shifted command key";6130case KEY_SCOPY:6131return "shifted copy key";6132case KEY_SCREATE:6133return "shifted create key";6134case KEY_SDC:6135return "shifted delete-character key";6136case KEY_SDL:6137return "shifted delete-line key";6138case KEY_SELECT:6139return "select key";6140case KEY_SEND:6141return "shifted end key";6142case KEY_SEOL:6143return "shifted clear-to-end-of-line key";6144case KEY_SEXIT:6145return "shifted exit key";6146case KEY_SFIND:6147return "shifted find key";6148case KEY_SHELP:6149return "shifted help key";6150case KEY_SHOME:6151return "shifted home key";6152case KEY_SIC:6153return "shifted insert-character key";6154case KEY_SLEFT:6155return "shifted left-arrow key";6156case KEY_SMESSAGE:6157return "shifted message key";6158case KEY_SMOVE:6159return "shifted move key";6160case KEY_SNEXT:6161return "shifted next key";6162case KEY_SOPTIONS:6163return "shifted options key";6164case KEY_SPREVIOUS:6165return "shifted previous key";6166case KEY_SPRINT:6167return "shifted print key";6168case KEY_SREDO:6169return "shifted redo key";6170case KEY_SREPLACE:6171return "shifted replace key";6172case KEY_SRIGHT:6173return "shifted right-arrow key";6174case KEY_SRSUME:6175return "shifted resume key";6176case KEY_SSAVE:6177return "shifted save key";6178case KEY_SSUSPEND:6179return "shifted suspend key";6180case KEY_SUNDO:6181return "shifted undo key";6182case KEY_SUSPEND:6183return "suspend key";6184case KEY_UNDO:6185return "undo key";6186case KEY_MOUSE:6187return "Mouse event has occurred";6188case KEY_RESIZE:6189return "Terminal resize event";6190#ifdef KEY_EVENT6191case KEY_EVENT:6192return "We were interrupted by an event";6193#endif6194case KEY_RETURN:6195return "return";6196case ' ':6197return "space";6198case '\t':6199return "tab";6200case KEY_ESCAPE:6201return "escape";6202default:6203if (llvm::isPrint(ch))6204snprintf(g_desc, sizeof(g_desc), "%c", ch);6205else6206snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch);6207return g_desc;6208}6209return nullptr;6210}62116212HelpDialogDelegate::HelpDialogDelegate(const char *text,6213KeyHelp *key_help_array)6214: m_text() {6215if (text && text[0]) {6216m_text.SplitIntoLines(text);6217m_text.AppendString("");6218}6219if (key_help_array) {6220for (KeyHelp *key = key_help_array; key->ch; ++key) {6221StreamString key_description;6222key_description.Printf("%10s - %s", CursesKeyToCString(key->ch),6223key->description);6224m_text.AppendString(key_description.GetString());6225}6226}6227}62286229HelpDialogDelegate::~HelpDialogDelegate() = default;62306231bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) {6232window.Erase();6233const int window_height = window.GetHeight();6234int x = 2;6235int y = 1;6236const int min_y = y;6237const int max_y = window_height - 1 - y;6238const size_t num_visible_lines = max_y - min_y + 1;6239const size_t num_lines = m_text.GetSize();6240const char *bottom_message;6241if (num_lines <= num_visible_lines)6242bottom_message = "Press any key to exit";6243else6244bottom_message = "Use arrows to scroll, any other key to exit";6245window.DrawTitleBox(window.GetName(), bottom_message);6246while (y <= max_y) {6247window.MoveCursor(x, y);6248window.PutCStringTruncated(62491, m_text.GetStringAtIndex(m_first_visible_line + y - min_y));6250++y;6251}6252return true;6253}62546255HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window,6256int key) {6257bool done = false;6258const size_t num_lines = m_text.GetSize();6259const size_t num_visible_lines = window.GetHeight() - 2;62606261if (num_lines <= num_visible_lines) {6262done = true;6263// If we have all lines visible and don't need scrolling, then any key6264// press will cause us to exit6265} else {6266switch (key) {6267case KEY_UP:6268if (m_first_visible_line > 0)6269--m_first_visible_line;6270break;62716272case KEY_DOWN:6273if (m_first_visible_line + num_visible_lines < num_lines)6274++m_first_visible_line;6275break;62766277case KEY_PPAGE:6278case ',':6279if (m_first_visible_line > 0) {6280if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines)6281m_first_visible_line -= num_visible_lines;6282else6283m_first_visible_line = 0;6284}6285break;62866287case KEY_NPAGE:6288case '.':6289if (m_first_visible_line + num_visible_lines < num_lines) {6290m_first_visible_line += num_visible_lines;6291if (static_cast<size_t>(m_first_visible_line) > num_lines)6292m_first_visible_line = num_lines - num_visible_lines;6293}6294break;62956296default:6297done = true;6298break;6299}6300}6301if (done)6302window.GetParent()->RemoveSubWindow(&window);6303return eKeyHandled;6304}63056306class ApplicationDelegate : public WindowDelegate, public MenuDelegate {6307public:6308enum {6309eMenuID_LLDB = 1,6310eMenuID_LLDBAbout,6311eMenuID_LLDBExit,63126313eMenuID_Target,6314eMenuID_TargetCreate,6315eMenuID_TargetDelete,63166317eMenuID_Process,6318eMenuID_ProcessAttach,6319eMenuID_ProcessDetachResume,6320eMenuID_ProcessDetachSuspended,6321eMenuID_ProcessLaunch,6322eMenuID_ProcessContinue,6323eMenuID_ProcessHalt,6324eMenuID_ProcessKill,63256326eMenuID_Thread,6327eMenuID_ThreadStepIn,6328eMenuID_ThreadStepOver,6329eMenuID_ThreadStepOut,63306331eMenuID_View,6332eMenuID_ViewBacktrace,6333eMenuID_ViewRegisters,6334eMenuID_ViewSource,6335eMenuID_ViewVariables,6336eMenuID_ViewBreakpoints,63376338eMenuID_Help,6339eMenuID_HelpGUIHelp6340};63416342ApplicationDelegate(Application &app, Debugger &debugger)6343: WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {}63446345~ApplicationDelegate() override = default;63466347bool WindowDelegateDraw(Window &window, bool force) override {6348return false; // Drawing not handled, let standard window drawing happen6349}63506351HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {6352switch (key) {6353case '\t':6354window.SelectNextWindowAsActive();6355return eKeyHandled;63566357case KEY_SHIFT_TAB:6358window.SelectPreviousWindowAsActive();6359return eKeyHandled;63606361case 'h':6362window.CreateHelpSubwindow();6363return eKeyHandled;63646365case KEY_ESCAPE:6366return eQuitApplication;63676368default:6369break;6370}6371return eKeyNotHandled;6372}63736374const char *WindowDelegateGetHelpText() override {6375return "Welcome to the LLDB curses GUI.\n\n"6376"Press the TAB key to change the selected view.\n"6377"Each view has its own keyboard shortcuts, press 'h' to open a "6378"dialog to display them.\n\n"6379"Common key bindings for all views:";6380}63816382KeyHelp *WindowDelegateGetKeyHelp() override {6383static curses::KeyHelp g_source_view_key_help[] = {6384{'\t', "Select next view"},6385{KEY_BTAB, "Select previous view"},6386{'h', "Show help dialog with view specific key bindings"},6387{',', "Page up"},6388{'.', "Page down"},6389{KEY_UP, "Select previous"},6390{KEY_DOWN, "Select next"},6391{KEY_LEFT, "Unexpand or select parent"},6392{KEY_RIGHT, "Expand"},6393{KEY_PPAGE, "Page up"},6394{KEY_NPAGE, "Page down"},6395{'\0', nullptr}};6396return g_source_view_key_help;6397}63986399MenuActionResult MenuDelegateAction(Menu &menu) override {6400switch (menu.GetIdentifier()) {6401case eMenuID_TargetCreate: {6402WindowSP main_window_sp = m_app.GetMainWindow();6403FormDelegateSP form_delegate_sp =6404FormDelegateSP(new TargetCreateFormDelegate(m_debugger));6405Rect bounds = main_window_sp->GetCenteredRect(80, 19);6406WindowSP form_window_sp = main_window_sp->CreateSubWindow(6407form_delegate_sp->GetName().c_str(), bounds, true);6408WindowDelegateSP window_delegate_sp =6409WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));6410form_window_sp->SetDelegate(window_delegate_sp);6411return MenuActionResult::Handled;6412}6413case eMenuID_ThreadStepIn: {6414ExecutionContext exe_ctx =6415m_debugger.GetCommandInterpreter().GetExecutionContext();6416if (exe_ctx.HasThreadScope()) {6417Process *process = exe_ctx.GetProcessPtr();6418if (process && process->IsAlive() &&6419StateIsStoppedState(process->GetState(), true))6420exe_ctx.GetThreadRef().StepIn(true);6421}6422}6423return MenuActionResult::Handled;64246425case eMenuID_ThreadStepOut: {6426ExecutionContext exe_ctx =6427m_debugger.GetCommandInterpreter().GetExecutionContext();6428if (exe_ctx.HasThreadScope()) {6429Process *process = exe_ctx.GetProcessPtr();6430if (process && process->IsAlive() &&6431StateIsStoppedState(process->GetState(), true)) {6432Thread *thread = exe_ctx.GetThreadPtr();6433uint32_t frame_idx =6434thread->GetSelectedFrameIndex(SelectMostRelevantFrame);6435exe_ctx.GetThreadRef().StepOut(frame_idx);6436}6437}6438}6439return MenuActionResult::Handled;64406441case eMenuID_ThreadStepOver: {6442ExecutionContext exe_ctx =6443m_debugger.GetCommandInterpreter().GetExecutionContext();6444if (exe_ctx.HasThreadScope()) {6445Process *process = exe_ctx.GetProcessPtr();6446if (process && process->IsAlive() &&6447StateIsStoppedState(process->GetState(), true))6448exe_ctx.GetThreadRef().StepOver(true);6449}6450}6451return MenuActionResult::Handled;64526453case eMenuID_ProcessAttach: {6454WindowSP main_window_sp = m_app.GetMainWindow();6455FormDelegateSP form_delegate_sp = FormDelegateSP(6456new ProcessAttachFormDelegate(m_debugger, main_window_sp));6457Rect bounds = main_window_sp->GetCenteredRect(80, 22);6458WindowSP form_window_sp = main_window_sp->CreateSubWindow(6459form_delegate_sp->GetName().c_str(), bounds, true);6460WindowDelegateSP window_delegate_sp =6461WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));6462form_window_sp->SetDelegate(window_delegate_sp);6463return MenuActionResult::Handled;6464}6465case eMenuID_ProcessLaunch: {6466WindowSP main_window_sp = m_app.GetMainWindow();6467FormDelegateSP form_delegate_sp = FormDelegateSP(6468new ProcessLaunchFormDelegate(m_debugger, main_window_sp));6469Rect bounds = main_window_sp->GetCenteredRect(80, 22);6470WindowSP form_window_sp = main_window_sp->CreateSubWindow(6471form_delegate_sp->GetName().c_str(), bounds, true);6472WindowDelegateSP window_delegate_sp =6473WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));6474form_window_sp->SetDelegate(window_delegate_sp);6475return MenuActionResult::Handled;6476}64776478case eMenuID_ProcessContinue: {6479ExecutionContext exe_ctx =6480m_debugger.GetCommandInterpreter().GetExecutionContext();6481if (exe_ctx.HasProcessScope()) {6482Process *process = exe_ctx.GetProcessPtr();6483if (process && process->IsAlive() &&6484StateIsStoppedState(process->GetState(), true))6485process->Resume();6486}6487}6488return MenuActionResult::Handled;64896490case eMenuID_ProcessKill: {6491ExecutionContext exe_ctx =6492m_debugger.GetCommandInterpreter().GetExecutionContext();6493if (exe_ctx.HasProcessScope()) {6494Process *process = exe_ctx.GetProcessPtr();6495if (process && process->IsAlive())6496process->Destroy(false);6497}6498}6499return MenuActionResult::Handled;65006501case eMenuID_ProcessHalt: {6502ExecutionContext exe_ctx =6503m_debugger.GetCommandInterpreter().GetExecutionContext();6504if (exe_ctx.HasProcessScope()) {6505Process *process = exe_ctx.GetProcessPtr();6506if (process && process->IsAlive())6507process->Halt();6508}6509}6510return MenuActionResult::Handled;65116512case eMenuID_ProcessDetachResume:6513case eMenuID_ProcessDetachSuspended: {6514ExecutionContext exe_ctx =6515m_debugger.GetCommandInterpreter().GetExecutionContext();6516if (exe_ctx.HasProcessScope()) {6517Process *process = exe_ctx.GetProcessPtr();6518if (process && process->IsAlive())6519process->Detach(menu.GetIdentifier() ==6520eMenuID_ProcessDetachSuspended);6521}6522}6523return MenuActionResult::Handled;65246525case eMenuID_Process: {6526// Populate the menu with all of the threads if the process is stopped6527// when the Process menu gets selected and is about to display its6528// submenu.6529Menus &submenus = menu.GetSubmenus();6530ExecutionContext exe_ctx =6531m_debugger.GetCommandInterpreter().GetExecutionContext();6532Process *process = exe_ctx.GetProcessPtr();6533if (process && process->IsAlive() &&6534StateIsStoppedState(process->GetState(), true)) {6535if (submenus.size() == 7)6536menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));6537else if (submenus.size() > 8)6538submenus.erase(submenus.begin() + 8, submenus.end());65396540ThreadList &threads = process->GetThreadList();6541std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());6542size_t num_threads = threads.GetSize();6543for (size_t i = 0; i < num_threads; ++i) {6544ThreadSP thread_sp = threads.GetThreadAtIndex(i);6545char menu_char = '\0';6546if (i < 9)6547menu_char = '1' + i;6548StreamString thread_menu_title;6549thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID());6550const char *thread_name = thread_sp->GetName();6551if (thread_name && thread_name[0])6552thread_menu_title.Printf(" %s", thread_name);6553else {6554const char *queue_name = thread_sp->GetQueueName();6555if (queue_name && queue_name[0])6556thread_menu_title.Printf(" %s", queue_name);6557}6558menu.AddSubmenu(6559MenuSP(new Menu(thread_menu_title.GetString().str().c_str(),6560nullptr, menu_char, thread_sp->GetID())));6561}6562} else if (submenus.size() > 7) {6563// Remove the separator and any other thread submenu items that were6564// previously added6565submenus.erase(submenus.begin() + 7, submenus.end());6566}6567// Since we are adding and removing items we need to recalculate the6568// name lengths6569menu.RecalculateNameLengths();6570}6571return MenuActionResult::Handled;65726573case eMenuID_ViewVariables: {6574WindowSP main_window_sp = m_app.GetMainWindow();6575WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");6576WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");6577WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");6578const Rect source_bounds = source_window_sp->GetBounds();65796580if (variables_window_sp) {6581const Rect variables_bounds = variables_window_sp->GetBounds();65826583main_window_sp->RemoveSubWindow(variables_window_sp.get());65846585if (registers_window_sp) {6586// We have a registers window, so give all the area back to the6587// registers window6588Rect registers_bounds = variables_bounds;6589registers_bounds.size.width = source_bounds.size.width;6590registers_window_sp->SetBounds(registers_bounds);6591} else {6592// We have no registers window showing so give the bottom area back6593// to the source view6594source_window_sp->Resize(source_bounds.size.width,6595source_bounds.size.height +6596variables_bounds.size.height);6597}6598} else {6599Rect new_variables_rect;6600if (registers_window_sp) {6601// We have a registers window so split the area of the registers6602// window into two columns where the left hand side will be the6603// variables and the right hand side will be the registers6604const Rect variables_bounds = registers_window_sp->GetBounds();6605Rect new_registers_rect;6606variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect,6607new_registers_rect);6608registers_window_sp->SetBounds(new_registers_rect);6609} else {6610// No registers window, grab the bottom part of the source window6611Rect new_source_rect;6612source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,6613new_variables_rect);6614source_window_sp->SetBounds(new_source_rect);6615}6616WindowSP new_window_sp = main_window_sp->CreateSubWindow(6617"Variables", new_variables_rect, false);6618new_window_sp->SetDelegate(6619WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));6620}6621touchwin(stdscr);6622}6623return MenuActionResult::Handled;66246625case eMenuID_ViewRegisters: {6626WindowSP main_window_sp = m_app.GetMainWindow();6627WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");6628WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");6629WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");6630const Rect source_bounds = source_window_sp->GetBounds();66316632if (registers_window_sp) {6633if (variables_window_sp) {6634const Rect variables_bounds = variables_window_sp->GetBounds();66356636// We have a variables window, so give all the area back to the6637// variables window6638variables_window_sp->Resize(variables_bounds.size.width +6639registers_window_sp->GetWidth(),6640variables_bounds.size.height);6641} else {6642// We have no variables window showing so give the bottom area back6643// to the source view6644source_window_sp->Resize(source_bounds.size.width,6645source_bounds.size.height +6646registers_window_sp->GetHeight());6647}6648main_window_sp->RemoveSubWindow(registers_window_sp.get());6649} else {6650Rect new_regs_rect;6651if (variables_window_sp) {6652// We have a variables window, split it into two columns where the6653// left hand side will be the variables and the right hand side will6654// be the registers6655const Rect variables_bounds = variables_window_sp->GetBounds();6656Rect new_vars_rect;6657variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect,6658new_regs_rect);6659variables_window_sp->SetBounds(new_vars_rect);6660} else {6661// No variables window, grab the bottom part of the source window6662Rect new_source_rect;6663source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,6664new_regs_rect);6665source_window_sp->SetBounds(new_source_rect);6666}6667WindowSP new_window_sp =6668main_window_sp->CreateSubWindow("Registers", new_regs_rect, false);6669new_window_sp->SetDelegate(6670WindowDelegateSP(new RegistersWindowDelegate(m_debugger)));6671}6672touchwin(stdscr);6673}6674return MenuActionResult::Handled;66756676case eMenuID_ViewBreakpoints: {6677WindowSP main_window_sp = m_app.GetMainWindow();6678WindowSP threads_window_sp = main_window_sp->FindSubWindow("Threads");6679WindowSP breakpoints_window_sp =6680main_window_sp->FindSubWindow("Breakpoints");6681const Rect threads_bounds = threads_window_sp->GetBounds();66826683// If a breakpoints window already exists, remove it and give the area6684// it used to occupy to the threads window. If it doesn't exist, split6685// the threads window horizontally into two windows where the top window6686// is the threads window and the bottom window is a newly added6687// breakpoints window.6688if (breakpoints_window_sp) {6689threads_window_sp->Resize(threads_bounds.size.width,6690threads_bounds.size.height +6691breakpoints_window_sp->GetHeight());6692main_window_sp->RemoveSubWindow(breakpoints_window_sp.get());6693} else {6694Rect new_threads_bounds, breakpoints_bounds;6695threads_bounds.HorizontalSplitPercentage(0.70, new_threads_bounds,6696breakpoints_bounds);6697threads_window_sp->SetBounds(new_threads_bounds);6698breakpoints_window_sp = main_window_sp->CreateSubWindow(6699"Breakpoints", breakpoints_bounds, false);6700TreeDelegateSP breakpoints_delegate_sp(6701new BreakpointsTreeDelegate(m_debugger));6702breakpoints_window_sp->SetDelegate(WindowDelegateSP(6703new TreeWindowDelegate(m_debugger, breakpoints_delegate_sp)));6704}6705touchwin(stdscr);6706return MenuActionResult::Handled;6707}67086709case eMenuID_HelpGUIHelp:6710m_app.GetMainWindow()->CreateHelpSubwindow();6711return MenuActionResult::Handled;67126713default:6714break;6715}67166717return MenuActionResult::NotHandled;6718}67196720protected:6721Application &m_app;6722Debugger &m_debugger;6723};67246725class StatusBarWindowDelegate : public WindowDelegate {6726public:6727StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) {6728FormatEntity::Parse("Thread: ${thread.id%tid}", m_format);6729}67306731~StatusBarWindowDelegate() override = default;67326733bool WindowDelegateDraw(Window &window, bool force) override {6734ExecutionContext exe_ctx =6735m_debugger.GetCommandInterpreter().GetExecutionContext();6736Process *process = exe_ctx.GetProcessPtr();6737Thread *thread = exe_ctx.GetThreadPtr();6738StackFrame *frame = exe_ctx.GetFramePtr();6739window.Erase();6740window.SetBackground(BlackOnWhite);6741window.MoveCursor(0, 0);6742if (process) {6743const StateType state = process->GetState();6744window.Printf("Process: %5" PRIu64 " %10s", process->GetID(),6745StateAsCString(state));67466747if (StateIsStoppedState(state, true)) {6748StreamString strm;6749if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx,6750nullptr, nullptr, false, false)) {6751window.MoveCursor(40, 0);6752window.PutCStringTruncated(1, strm.GetString().str().c_str());6753}67546755window.MoveCursor(60, 0);6756if (frame)6757window.Printf("Frame: %3u PC = 0x%16.16" PRIx64,6758frame->GetFrameIndex(),6759frame->GetFrameCodeAddress().GetOpcodeLoadAddress(6760exe_ctx.GetTargetPtr()));6761} else if (state == eStateExited) {6762const char *exit_desc = process->GetExitDescription();6763const int exit_status = process->GetExitStatus();6764if (exit_desc && exit_desc[0])6765window.Printf(" with status = %i (%s)", exit_status, exit_desc);6766else6767window.Printf(" with status = %i", exit_status);6768}6769}6770return true;6771}67726773protected:6774Debugger &m_debugger;6775FormatEntity::Entry m_format;6776};67776778class SourceFileWindowDelegate : public WindowDelegate {6779public:6780SourceFileWindowDelegate(Debugger &debugger)6781: WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(),6782m_disassembly_sp(), m_disassembly_range(), m_title() {}67836784~SourceFileWindowDelegate() override = default;67856786void Update(const SymbolContext &sc) { m_sc = sc; }67876788uint32_t NumVisibleLines() const { return m_max_y - m_min_y; }67896790const char *WindowDelegateGetHelpText() override {6791return "Source/Disassembly window keyboard shortcuts:";6792}67936794KeyHelp *WindowDelegateGetKeyHelp() override {6795static curses::KeyHelp g_source_view_key_help[] = {6796{KEY_RETURN, "Run to selected line with one shot breakpoint"},6797{KEY_UP, "Select previous source line"},6798{KEY_DOWN, "Select next source line"},6799{KEY_LEFT, "Scroll to the left"},6800{KEY_RIGHT, "Scroll to the right"},6801{KEY_PPAGE, "Page up"},6802{KEY_NPAGE, "Page down"},6803{'b', "Set breakpoint on selected source/disassembly line"},6804{'c', "Continue process"},6805{'D', "Detach with process suspended"},6806{'h', "Show help dialog"},6807{'n', "Step over (source line)"},6808{'N', "Step over (single instruction)"},6809{'f', "Step out (finish)"},6810{'s', "Step in (source line)"},6811{'S', "Step in (single instruction)"},6812{'u', "Frame up"},6813{'d', "Frame down"},6814{',', "Page up"},6815{'.', "Page down"},6816{'\0', nullptr}};6817return g_source_view_key_help;6818}68196820bool WindowDelegateDraw(Window &window, bool force) override {6821ExecutionContext exe_ctx =6822m_debugger.GetCommandInterpreter().GetExecutionContext();6823Process *process = exe_ctx.GetProcessPtr();6824Thread *thread = nullptr;68256826bool update_location = false;6827if (process) {6828StateType state = process->GetState();6829if (StateIsStoppedState(state, true)) {6830// We are stopped, so it is ok to6831update_location = true;6832}6833}68346835m_min_x = 1;6836m_min_y = 2;6837m_max_x = window.GetMaxX() - 1;6838m_max_y = window.GetMaxY() - 1;68396840const uint32_t num_visible_lines = NumVisibleLines();6841StackFrameSP frame_sp;6842bool set_selected_line_to_pc = false;68436844if (update_location) {6845const bool process_alive = process->IsAlive();6846bool thread_changed = false;6847if (process_alive) {6848thread = exe_ctx.GetThreadPtr();6849if (thread) {6850frame_sp = thread->GetSelectedFrame(SelectMostRelevantFrame);6851auto tid = thread->GetID();6852thread_changed = tid != m_tid;6853m_tid = tid;6854} else {6855if (m_tid != LLDB_INVALID_THREAD_ID) {6856thread_changed = true;6857m_tid = LLDB_INVALID_THREAD_ID;6858}6859}6860}6861const uint32_t stop_id = process ? process->GetStopID() : 0;6862const bool stop_id_changed = stop_id != m_stop_id;6863bool frame_changed = false;6864m_stop_id = stop_id;6865m_title.Clear();6866if (frame_sp) {6867m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything);6868if (m_sc.module_sp) {6869m_title.Printf(6870"%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString());6871ConstString func_name = m_sc.GetFunctionName();6872if (func_name)6873m_title.Printf("`%s", func_name.GetCString());6874}6875const uint32_t frame_idx = frame_sp->GetFrameIndex();6876frame_changed = frame_idx != m_frame_idx;6877m_frame_idx = frame_idx;6878} else {6879m_sc.Clear(true);6880frame_changed = m_frame_idx != UINT32_MAX;6881m_frame_idx = UINT32_MAX;6882}68836884const bool context_changed =6885thread_changed || frame_changed || stop_id_changed;68866887if (process_alive) {6888if (m_sc.line_entry.IsValid()) {6889m_pc_line = m_sc.line_entry.line;6890if (m_pc_line != UINT32_MAX)6891--m_pc_line; // Convert to zero based line number...6892// Update the selected line if the stop ID changed...6893if (context_changed)6894m_selected_line = m_pc_line;68956896if (m_file_sp &&6897m_file_sp->GetFileSpec() == m_sc.line_entry.GetFile()) {6898// Same file, nothing to do, we should either have the lines or6899// not (source file missing)6900if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) {6901if (m_selected_line >= m_first_visible_line + num_visible_lines)6902m_first_visible_line = m_selected_line - 10;6903} else {6904if (m_selected_line > 10)6905m_first_visible_line = m_selected_line - 10;6906else6907m_first_visible_line = 0;6908}6909} else {6910// File changed, set selected line to the line with the PC6911m_selected_line = m_pc_line;6912m_file_sp = m_debugger.GetSourceManager().GetFile(6913m_sc.line_entry.GetFile());6914if (m_file_sp) {6915const size_t num_lines = m_file_sp->GetNumLines();6916m_line_width = 1;6917for (size_t n = num_lines; n >= 10; n = n / 10)6918++m_line_width;69196920if (num_lines < num_visible_lines ||6921m_selected_line < num_visible_lines)6922m_first_visible_line = 0;6923else6924m_first_visible_line = m_selected_line - 10;6925}6926}6927} else {6928m_file_sp.reset();6929}69306931if (!m_file_sp || m_file_sp->GetNumLines() == 0) {6932// Show disassembly6933bool prefer_file_cache = false;6934if (m_sc.function) {6935if (m_disassembly_scope != m_sc.function) {6936m_disassembly_scope = m_sc.function;6937m_disassembly_sp = m_sc.function->GetInstructions(6938exe_ctx, nullptr, !prefer_file_cache);6939if (m_disassembly_sp) {6940set_selected_line_to_pc = true;6941m_disassembly_range = m_sc.function->GetAddressRange();6942} else {6943m_disassembly_range.Clear();6944}6945} else {6946set_selected_line_to_pc = context_changed;6947}6948} else if (m_sc.symbol) {6949if (m_disassembly_scope != m_sc.symbol) {6950m_disassembly_scope = m_sc.symbol;6951m_disassembly_sp = m_sc.symbol->GetInstructions(6952exe_ctx, nullptr, prefer_file_cache);6953if (m_disassembly_sp) {6954set_selected_line_to_pc = true;6955m_disassembly_range.GetBaseAddress() =6956m_sc.symbol->GetAddress();6957m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize());6958} else {6959m_disassembly_range.Clear();6960}6961} else {6962set_selected_line_to_pc = context_changed;6963}6964}6965}6966} else {6967m_pc_line = UINT32_MAX;6968}6969}69706971const int window_width = window.GetWidth();6972window.Erase();6973window.DrawTitleBox("Sources");6974if (!m_title.GetString().empty()) {6975window.AttributeOn(A_REVERSE);6976window.MoveCursor(1, 1);6977window.PutChar(' ');6978window.PutCStringTruncated(1, m_title.GetString().str().c_str());6979int x = window.GetCursorX();6980if (x < window_width - 1) {6981window.Printf("%*s", window_width - x - 1, "");6982}6983window.AttributeOff(A_REVERSE);6984}69856986Target *target = exe_ctx.GetTargetPtr();6987const size_t num_source_lines = GetNumSourceLines();6988if (num_source_lines > 0) {6989// Display source6990BreakpointLines bp_lines;6991if (target) {6992BreakpointList &bp_list = target->GetBreakpointList();6993const size_t num_bps = bp_list.GetSize();6994for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {6995BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);6996const size_t num_bps_locs = bp_sp->GetNumLocations();6997for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {6998BreakpointLocationSP bp_loc_sp =6999bp_sp->GetLocationAtIndex(bp_loc_idx);7000LineEntry bp_loc_line_entry;7001if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(7002bp_loc_line_entry)) {7003if (m_file_sp->GetFileSpec() == bp_loc_line_entry.GetFile()) {7004bp_lines.insert(bp_loc_line_entry.line);7005}7006}7007}7008}7009}70107011for (size_t i = 0; i < num_visible_lines; ++i) {7012const uint32_t curr_line = m_first_visible_line + i;7013if (curr_line < num_source_lines) {7014const int line_y = m_min_y + i;7015window.MoveCursor(1, line_y);7016const bool is_pc_line = curr_line == m_pc_line;7017const bool line_is_selected = m_selected_line == curr_line;7018// Highlight the line as the PC line first (done by passing7019// argument to OutputColoredStringTruncated()), then if the selected7020// line isn't the same as the PC line, highlight it differently.7021attr_t highlight_attr = 0;7022attr_t bp_attr = 0;7023if (line_is_selected && !is_pc_line)7024highlight_attr = A_REVERSE;70257026if (bp_lines.find(curr_line + 1) != bp_lines.end())7027bp_attr = COLOR_PAIR(BlackOnWhite);70287029if (bp_attr)7030window.AttributeOn(bp_attr);70317032window.Printf(" %*u ", m_line_width, curr_line + 1);70337034if (bp_attr)7035window.AttributeOff(bp_attr);70367037window.PutChar(ACS_VLINE);7038// Mark the line with the PC with a diamond7039if (is_pc_line)7040window.PutChar(ACS_DIAMOND);7041else7042window.PutChar(' ');70437044if (highlight_attr)7045window.AttributeOn(highlight_attr);70467047StreamString lineStream;70487049std::optional<size_t> column;7050if (is_pc_line && m_sc.line_entry.IsValid() && m_sc.line_entry.column)7051column = m_sc.line_entry.column - 1;7052m_file_sp->DisplaySourceLines(curr_line + 1, column, 0, 0,7053&lineStream);7054StringRef line = lineStream.GetString();7055if (line.ends_with("\n"))7056line = line.drop_back();7057bool wasWritten = window.OutputColoredStringTruncated(70581, line, m_first_visible_column, is_pc_line);7059if (!wasWritten && (line_is_selected || is_pc_line)) {7060// Draw an empty space to show the selected/PC line if empty,7061// or draw '<' if nothing is visible because of scrolling too much7062// to the right.7063window.PutCStringTruncated(70641, line.empty() && m_first_visible_column == 0 ? " " : "<");7065}70667067if (is_pc_line && frame_sp &&7068frame_sp->GetConcreteFrameIndex() == 0) {7069StopInfoSP stop_info_sp;7070if (thread)7071stop_info_sp = thread->GetStopInfo();7072if (stop_info_sp) {7073const char *stop_description = stop_info_sp->GetDescription();7074if (stop_description && stop_description[0]) {7075size_t stop_description_len = strlen(stop_description);7076int desc_x = window_width - stop_description_len - 16;7077if (desc_x - window.GetCursorX() > 0)7078window.Printf("%*s", desc_x - window.GetCursorX(), "");7079window.MoveCursor(window_width - stop_description_len - 16,7080line_y);7081const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue);7082window.AttributeOn(stop_reason_attr);7083window.PrintfTruncated(1, " <<< Thread %u: %s ",7084thread->GetIndexID(), stop_description);7085window.AttributeOff(stop_reason_attr);7086}7087} else {7088window.Printf("%*s", window_width - window.GetCursorX() - 1, "");7089}7090}7091if (highlight_attr)7092window.AttributeOff(highlight_attr);7093} else {7094break;7095}7096}7097} else {7098size_t num_disassembly_lines = GetNumDisassemblyLines();7099if (num_disassembly_lines > 0) {7100// Display disassembly7101BreakpointAddrs bp_file_addrs;7102Target *target = exe_ctx.GetTargetPtr();7103if (target) {7104BreakpointList &bp_list = target->GetBreakpointList();7105const size_t num_bps = bp_list.GetSize();7106for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {7107BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);7108const size_t num_bps_locs = bp_sp->GetNumLocations();7109for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs;7110++bp_loc_idx) {7111BreakpointLocationSP bp_loc_sp =7112bp_sp->GetLocationAtIndex(bp_loc_idx);7113LineEntry bp_loc_line_entry;7114const lldb::addr_t file_addr =7115bp_loc_sp->GetAddress().GetFileAddress();7116if (file_addr != LLDB_INVALID_ADDRESS) {7117if (m_disassembly_range.ContainsFileAddress(file_addr))7118bp_file_addrs.insert(file_addr);7119}7120}7121}7122}71237124const attr_t selected_highlight_attr = A_REVERSE;7125const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue);71267127StreamString strm;71287129InstructionList &insts = m_disassembly_sp->GetInstructionList();7130Address pc_address;71317132if (frame_sp)7133pc_address = frame_sp->GetFrameCodeAddress();7134const uint32_t pc_idx =7135pc_address.IsValid()7136? insts.GetIndexOfInstructionAtAddress(pc_address)7137: UINT32_MAX;7138if (set_selected_line_to_pc) {7139m_selected_line = pc_idx;7140}71417142const uint32_t non_visible_pc_offset = (num_visible_lines / 5);7143if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines)7144m_first_visible_line = 0;71457146if (pc_idx < num_disassembly_lines) {7147if (pc_idx < static_cast<uint32_t>(m_first_visible_line) ||7148pc_idx >= m_first_visible_line + num_visible_lines)7149m_first_visible_line = pc_idx - non_visible_pc_offset;7150}71517152for (size_t i = 0; i < num_visible_lines; ++i) {7153const uint32_t inst_idx = m_first_visible_line + i;7154Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get();7155if (!inst)7156break;71577158const int line_y = m_min_y + i;7159window.MoveCursor(1, line_y);7160const bool is_pc_line = frame_sp && inst_idx == pc_idx;7161const bool line_is_selected = m_selected_line == inst_idx;7162// Highlight the line as the PC line first, then if the selected7163// line isn't the same as the PC line, highlight it differently7164attr_t highlight_attr = 0;7165attr_t bp_attr = 0;7166if (is_pc_line)7167highlight_attr = pc_highlight_attr;7168else if (line_is_selected)7169highlight_attr = selected_highlight_attr;71707171if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) !=7172bp_file_addrs.end())7173bp_attr = COLOR_PAIR(BlackOnWhite);71747175if (bp_attr)7176window.AttributeOn(bp_attr);71777178window.Printf(" 0x%16.16llx ",7179static_cast<unsigned long long>(7180inst->GetAddress().GetLoadAddress(target)));71817182if (bp_attr)7183window.AttributeOff(bp_attr);71847185window.PutChar(ACS_VLINE);7186// Mark the line with the PC with a diamond7187if (is_pc_line)7188window.PutChar(ACS_DIAMOND);7189else7190window.PutChar(' ');71917192if (highlight_attr)7193window.AttributeOn(highlight_attr);71947195const char *mnemonic = inst->GetMnemonic(&exe_ctx);7196const char *operands = inst->GetOperands(&exe_ctx);7197const char *comment = inst->GetComment(&exe_ctx);71987199if (mnemonic != nullptr && mnemonic[0] == '\0')7200mnemonic = nullptr;7201if (operands != nullptr && operands[0] == '\0')7202operands = nullptr;7203if (comment != nullptr && comment[0] == '\0')7204comment = nullptr;72057206strm.Clear();72077208if (mnemonic != nullptr && operands != nullptr && comment != nullptr)7209strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment);7210else if (mnemonic != nullptr && operands != nullptr)7211strm.Printf("%-8s %s", mnemonic, operands);7212else if (mnemonic != nullptr)7213strm.Printf("%s", mnemonic);72147215int right_pad = 1;7216window.PutCStringTruncated(7217right_pad,7218strm.GetString().substr(m_first_visible_column).data());72197220if (is_pc_line && frame_sp &&7221frame_sp->GetConcreteFrameIndex() == 0) {7222StopInfoSP stop_info_sp;7223if (thread)7224stop_info_sp = thread->GetStopInfo();7225if (stop_info_sp) {7226const char *stop_description = stop_info_sp->GetDescription();7227if (stop_description && stop_description[0]) {7228size_t stop_description_len = strlen(stop_description);7229int desc_x = window_width - stop_description_len - 16;7230if (desc_x - window.GetCursorX() > 0)7231window.Printf("%*s", desc_x - window.GetCursorX(), "");7232window.MoveCursor(window_width - stop_description_len - 15,7233line_y);7234if (thread)7235window.PrintfTruncated(1, "<<< Thread %u: %s ",7236thread->GetIndexID(),7237stop_description);7238}7239} else {7240window.Printf("%*s", window_width - window.GetCursorX() - 1, "");7241}7242}7243if (highlight_attr)7244window.AttributeOff(highlight_attr);7245}7246}7247}7248return true; // Drawing handled7249}72507251size_t GetNumLines() {7252size_t num_lines = GetNumSourceLines();7253if (num_lines == 0)7254num_lines = GetNumDisassemblyLines();7255return num_lines;7256}72577258size_t GetNumSourceLines() const {7259if (m_file_sp)7260return m_file_sp->GetNumLines();7261return 0;7262}72637264size_t GetNumDisassemblyLines() const {7265if (m_disassembly_sp)7266return m_disassembly_sp->GetInstructionList().GetSize();7267return 0;7268}72697270HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {7271const uint32_t num_visible_lines = NumVisibleLines();7272const size_t num_lines = GetNumLines();72737274switch (c) {7275case ',':7276case KEY_PPAGE:7277// Page up key7278if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines)7279m_first_visible_line -= num_visible_lines;7280else7281m_first_visible_line = 0;7282m_selected_line = m_first_visible_line;7283return eKeyHandled;72847285case '.':7286case KEY_NPAGE:7287// Page down key7288{7289if (m_first_visible_line + num_visible_lines < num_lines)7290m_first_visible_line += num_visible_lines;7291else if (num_lines < num_visible_lines)7292m_first_visible_line = 0;7293else7294m_first_visible_line = num_lines - num_visible_lines;7295m_selected_line = m_first_visible_line;7296}7297return eKeyHandled;72987299case KEY_UP:7300if (m_selected_line > 0) {7301m_selected_line--;7302if (static_cast<size_t>(m_first_visible_line) > m_selected_line)7303m_first_visible_line = m_selected_line;7304}7305return eKeyHandled;73067307case KEY_DOWN:7308if (m_selected_line + 1 < num_lines) {7309m_selected_line++;7310if (m_first_visible_line + num_visible_lines < m_selected_line)7311m_first_visible_line++;7312}7313return eKeyHandled;73147315case KEY_LEFT:7316if (m_first_visible_column > 0)7317--m_first_visible_column;7318return eKeyHandled;73197320case KEY_RIGHT:7321++m_first_visible_column;7322return eKeyHandled;73237324case '\r':7325case '\n':7326case KEY_ENTER:7327// Set a breakpoint and run to the line using a one shot breakpoint7328if (GetNumSourceLines() > 0) {7329ExecutionContext exe_ctx =7330m_debugger.GetCommandInterpreter().GetExecutionContext();7331if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) {7332BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(7333nullptr, // Don't limit the breakpoint to certain modules7334m_file_sp->GetFileSpec(), // Source file7335m_selected_line +73361, // Source line number (m_selected_line is zero based)73370, // Unspecified column.73380, // No offset7339eLazyBoolCalculate, // Check inlines using global setting7340eLazyBoolCalculate, // Skip prologue using global setting,7341false, // internal7342false, // request_hardware7343eLazyBoolCalculate); // move_to_nearest_code7344// Make breakpoint one shot7345bp_sp->GetOptions().SetOneShot(true);7346exe_ctx.GetProcessRef().Resume();7347}7348} else if (m_selected_line < GetNumDisassemblyLines()) {7349const Instruction *inst = m_disassembly_sp->GetInstructionList()7350.GetInstructionAtIndex(m_selected_line)7351.get();7352ExecutionContext exe_ctx =7353m_debugger.GetCommandInterpreter().GetExecutionContext();7354if (exe_ctx.HasTargetScope()) {7355Address addr = inst->GetAddress();7356BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(7357addr, // lldb_private::Address7358false, // internal7359false); // request_hardware7360// Make breakpoint one shot7361bp_sp->GetOptions().SetOneShot(true);7362exe_ctx.GetProcessRef().Resume();7363}7364}7365return eKeyHandled;73667367case 'b': // 'b' == toggle breakpoint on currently selected line7368ToggleBreakpointOnSelectedLine();7369return eKeyHandled;73707371case 'D': // 'D' == detach and keep stopped7372{7373ExecutionContext exe_ctx =7374m_debugger.GetCommandInterpreter().GetExecutionContext();7375if (exe_ctx.HasProcessScope())7376exe_ctx.GetProcessRef().Detach(true);7377}7378return eKeyHandled;73797380case 'c':7381// 'c' == continue7382{7383ExecutionContext exe_ctx =7384m_debugger.GetCommandInterpreter().GetExecutionContext();7385if (exe_ctx.HasProcessScope())7386exe_ctx.GetProcessRef().Resume();7387}7388return eKeyHandled;73897390case 'f':7391// 'f' == step out (finish)7392{7393ExecutionContext exe_ctx =7394m_debugger.GetCommandInterpreter().GetExecutionContext();7395if (exe_ctx.HasThreadScope() &&7396StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {7397Thread *thread = exe_ctx.GetThreadPtr();7398uint32_t frame_idx =7399thread->GetSelectedFrameIndex(SelectMostRelevantFrame);7400exe_ctx.GetThreadRef().StepOut(frame_idx);7401}7402}7403return eKeyHandled;74047405case 'n': // 'n' == step over7406case 'N': // 'N' == step over instruction7407{7408ExecutionContext exe_ctx =7409m_debugger.GetCommandInterpreter().GetExecutionContext();7410if (exe_ctx.HasThreadScope() &&7411StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {7412bool source_step = (c == 'n');7413exe_ctx.GetThreadRef().StepOver(source_step);7414}7415}7416return eKeyHandled;74177418case 's': // 's' == step into7419case 'S': // 'S' == step into instruction7420{7421ExecutionContext exe_ctx =7422m_debugger.GetCommandInterpreter().GetExecutionContext();7423if (exe_ctx.HasThreadScope() &&7424StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {7425bool source_step = (c == 's');7426exe_ctx.GetThreadRef().StepIn(source_step);7427}7428}7429return eKeyHandled;74307431case 'u': // 'u' == frame up7432case 'd': // 'd' == frame down7433{7434ExecutionContext exe_ctx =7435m_debugger.GetCommandInterpreter().GetExecutionContext();7436if (exe_ctx.HasThreadScope()) {7437Thread *thread = exe_ctx.GetThreadPtr();7438uint32_t frame_idx =7439thread->GetSelectedFrameIndex(SelectMostRelevantFrame);7440if (frame_idx == UINT32_MAX)7441frame_idx = 0;7442if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount())7443++frame_idx;7444else if (c == 'd' && frame_idx > 0)7445--frame_idx;7446if (thread->SetSelectedFrameByIndex(frame_idx, true))7447exe_ctx.SetFrameSP(thread->GetSelectedFrame(SelectMostRelevantFrame));7448}7449}7450return eKeyHandled;74517452case 'h':7453window.CreateHelpSubwindow();7454return eKeyHandled;74557456default:7457break;7458}7459return eKeyNotHandled;7460}74617462void ToggleBreakpointOnSelectedLine() {7463ExecutionContext exe_ctx =7464m_debugger.GetCommandInterpreter().GetExecutionContext();7465if (!exe_ctx.HasTargetScope())7466return;7467if (GetNumSourceLines() > 0) {7468// Source file breakpoint.7469BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();7470const size_t num_bps = bp_list.GetSize();7471for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {7472BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);7473const size_t num_bps_locs = bp_sp->GetNumLocations();7474for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {7475BreakpointLocationSP bp_loc_sp =7476bp_sp->GetLocationAtIndex(bp_loc_idx);7477LineEntry bp_loc_line_entry;7478if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(7479bp_loc_line_entry)) {7480if (m_file_sp->GetFileSpec() == bp_loc_line_entry.GetFile() &&7481m_selected_line + 1 == bp_loc_line_entry.line) {7482bool removed =7483exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());7484assert(removed);7485UNUSED_IF_ASSERT_DISABLED(removed);7486return; // Existing breakpoint removed.7487}7488}7489}7490}7491// No breakpoint found on the location, add it.7492BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(7493nullptr, // Don't limit the breakpoint to certain modules7494m_file_sp->GetFileSpec(), // Source file7495m_selected_line +74961, // Source line number (m_selected_line is zero based)74970, // No column specified.74980, // No offset7499eLazyBoolCalculate, // Check inlines using global setting7500eLazyBoolCalculate, // Skip prologue using global setting,7501false, // internal7502false, // request_hardware7503eLazyBoolCalculate); // move_to_nearest_code7504} else {7505// Disassembly breakpoint.7506assert(GetNumDisassemblyLines() > 0);7507assert(m_selected_line < GetNumDisassemblyLines());7508const Instruction *inst = m_disassembly_sp->GetInstructionList()7509.GetInstructionAtIndex(m_selected_line)7510.get();7511Address addr = inst->GetAddress();7512// Try to find it.7513BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();7514const size_t num_bps = bp_list.GetSize();7515for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {7516BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);7517const size_t num_bps_locs = bp_sp->GetNumLocations();7518for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {7519BreakpointLocationSP bp_loc_sp =7520bp_sp->GetLocationAtIndex(bp_loc_idx);7521LineEntry bp_loc_line_entry;7522const lldb::addr_t file_addr =7523bp_loc_sp->GetAddress().GetFileAddress();7524if (file_addr == addr.GetFileAddress()) {7525bool removed =7526exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());7527assert(removed);7528UNUSED_IF_ASSERT_DISABLED(removed);7529return; // Existing breakpoint removed.7530}7531}7532}7533// No breakpoint found on the address, add it.7534BreakpointSP bp_sp =7535exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address7536false, // internal7537false); // request_hardware7538}7539}75407541protected:7542typedef std::set<uint32_t> BreakpointLines;7543typedef std::set<lldb::addr_t> BreakpointAddrs;75447545Debugger &m_debugger;7546SymbolContext m_sc;7547SourceManager::FileSP m_file_sp;7548SymbolContextScope *m_disassembly_scope = nullptr;7549lldb::DisassemblerSP m_disassembly_sp;7550AddressRange m_disassembly_range;7551StreamString m_title;7552lldb::user_id_t m_tid = LLDB_INVALID_THREAD_ID;7553int m_line_width = 4;7554uint32_t m_selected_line = 0; // The selected line7555uint32_t m_pc_line = 0; // The line with the PC7556uint32_t m_stop_id = 0;7557uint32_t m_frame_idx = UINT32_MAX;7558int m_first_visible_line = 0;7559int m_first_visible_column = 0;7560int m_min_x = 0;7561int m_min_y = 0;7562int m_max_x = 0;7563int m_max_y = 0;7564};75657566DisplayOptions ValueObjectListDelegate::g_options = {true};75677568IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger)7569: IOHandler(debugger, IOHandler::Type::Curses) {}75707571void IOHandlerCursesGUI::Activate() {7572IOHandler::Activate();7573if (!m_app_ap) {7574m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE());75757576// This is both a window and a menu delegate7577std::shared_ptr<ApplicationDelegate> app_delegate_sp(7578new ApplicationDelegate(*m_app_ap, m_debugger));75797580MenuDelegateSP app_menu_delegate_sp =7581std::static_pointer_cast<MenuDelegate>(app_delegate_sp);7582MenuSP lldb_menu_sp(7583new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB));7584MenuSP exit_menuitem_sp(7585new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit));7586exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit);7587lldb_menu_sp->AddSubmenu(MenuSP(new Menu(7588"About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout)));7589lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));7590lldb_menu_sp->AddSubmenu(exit_menuitem_sp);75917592MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2),7593ApplicationDelegate::eMenuID_Target));7594target_menu_sp->AddSubmenu(MenuSP(new Menu(7595"Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate)));7596target_menu_sp->AddSubmenu(MenuSP(new Menu(7597"Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete)));75987599MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3),7600ApplicationDelegate::eMenuID_Process));7601process_menu_sp->AddSubmenu(MenuSP(new Menu(7602"Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach)));7603process_menu_sp->AddSubmenu(7604MenuSP(new Menu("Detach and resume", nullptr, 'd',7605ApplicationDelegate::eMenuID_ProcessDetachResume)));7606process_menu_sp->AddSubmenu(7607MenuSP(new Menu("Detach suspended", nullptr, 's',7608ApplicationDelegate::eMenuID_ProcessDetachSuspended)));7609process_menu_sp->AddSubmenu(MenuSP(new Menu(7610"Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch)));7611process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));7612process_menu_sp->AddSubmenu(7613MenuSP(new Menu("Continue", nullptr, 'c',7614ApplicationDelegate::eMenuID_ProcessContinue)));7615process_menu_sp->AddSubmenu(MenuSP(new Menu(7616"Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt)));7617process_menu_sp->AddSubmenu(MenuSP(new Menu(7618"Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill)));76197620MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4),7621ApplicationDelegate::eMenuID_Thread));7622thread_menu_sp->AddSubmenu(MenuSP(new Menu(7623"Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn)));7624thread_menu_sp->AddSubmenu(7625MenuSP(new Menu("Step Over", nullptr, 'v',7626ApplicationDelegate::eMenuID_ThreadStepOver)));7627thread_menu_sp->AddSubmenu(MenuSP(new Menu(7628"Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut)));76297630MenuSP view_menu_sp(7631new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View));7632view_menu_sp->AddSubmenu(7633MenuSP(new Menu("Backtrace", nullptr, 't',7634ApplicationDelegate::eMenuID_ViewBacktrace)));7635view_menu_sp->AddSubmenu(7636MenuSP(new Menu("Registers", nullptr, 'r',7637ApplicationDelegate::eMenuID_ViewRegisters)));7638view_menu_sp->AddSubmenu(MenuSP(new Menu(7639"Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource)));7640view_menu_sp->AddSubmenu(7641MenuSP(new Menu("Variables", nullptr, 'v',7642ApplicationDelegate::eMenuID_ViewVariables)));7643view_menu_sp->AddSubmenu(7644MenuSP(new Menu("Breakpoints", nullptr, 'b',7645ApplicationDelegate::eMenuID_ViewBreakpoints)));76467647MenuSP help_menu_sp(7648new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help));7649help_menu_sp->AddSubmenu(MenuSP(new Menu(7650"GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp)));76517652m_app_ap->Initialize();7653WindowSP &main_window_sp = m_app_ap->GetMainWindow();76547655MenuSP menubar_sp(new Menu(Menu::Type::Bar));7656menubar_sp->AddSubmenu(lldb_menu_sp);7657menubar_sp->AddSubmenu(target_menu_sp);7658menubar_sp->AddSubmenu(process_menu_sp);7659menubar_sp->AddSubmenu(thread_menu_sp);7660menubar_sp->AddSubmenu(view_menu_sp);7661menubar_sp->AddSubmenu(help_menu_sp);7662menubar_sp->SetDelegate(app_menu_delegate_sp);76637664Rect content_bounds = main_window_sp->GetFrame();7665Rect menubar_bounds = content_bounds.MakeMenuBar();7666Rect status_bounds = content_bounds.MakeStatusBar();7667Rect source_bounds;7668Rect variables_bounds;7669Rect threads_bounds;7670Rect source_variables_bounds;7671content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,7672threads_bounds);7673source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds,7674variables_bounds);76757676WindowSP menubar_window_sp =7677main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false);7678// Let the menubar get keys if the active window doesn't handle the keys7679// that are typed so it can respond to menubar key presses.7680menubar_window_sp->SetCanBeActive(7681false); // Don't let the menubar become the active window7682menubar_window_sp->SetDelegate(menubar_sp);76837684WindowSP source_window_sp(7685main_window_sp->CreateSubWindow("Source", source_bounds, true));7686WindowSP variables_window_sp(7687main_window_sp->CreateSubWindow("Variables", variables_bounds, false));7688WindowSP threads_window_sp(7689main_window_sp->CreateSubWindow("Threads", threads_bounds, false));7690WindowSP status_window_sp(7691main_window_sp->CreateSubWindow("Status", status_bounds, false));7692status_window_sp->SetCanBeActive(7693false); // Don't let the status bar become the active window7694main_window_sp->SetDelegate(7695std::static_pointer_cast<WindowDelegate>(app_delegate_sp));7696source_window_sp->SetDelegate(7697WindowDelegateSP(new SourceFileWindowDelegate(m_debugger)));7698variables_window_sp->SetDelegate(7699WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));7700TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger));7701threads_window_sp->SetDelegate(WindowDelegateSP(7702new TreeWindowDelegate(m_debugger, thread_delegate_sp)));7703status_window_sp->SetDelegate(7704WindowDelegateSP(new StatusBarWindowDelegate(m_debugger)));77057706// All colors with black background.7707init_pair(1, COLOR_BLACK, COLOR_BLACK);7708init_pair(2, COLOR_RED, COLOR_BLACK);7709init_pair(3, COLOR_GREEN, COLOR_BLACK);7710init_pair(4, COLOR_YELLOW, COLOR_BLACK);7711init_pair(5, COLOR_BLUE, COLOR_BLACK);7712init_pair(6, COLOR_MAGENTA, COLOR_BLACK);7713init_pair(7, COLOR_CYAN, COLOR_BLACK);7714init_pair(8, COLOR_WHITE, COLOR_BLACK);7715// All colors with blue background.7716init_pair(9, COLOR_BLACK, COLOR_BLUE);7717init_pair(10, COLOR_RED, COLOR_BLUE);7718init_pair(11, COLOR_GREEN, COLOR_BLUE);7719init_pair(12, COLOR_YELLOW, COLOR_BLUE);7720init_pair(13, COLOR_BLUE, COLOR_BLUE);7721init_pair(14, COLOR_MAGENTA, COLOR_BLUE);7722init_pair(15, COLOR_CYAN, COLOR_BLUE);7723init_pair(16, COLOR_WHITE, COLOR_BLUE);7724// These must match the order in the color indexes enum.7725init_pair(17, COLOR_BLACK, COLOR_WHITE);7726init_pair(18, COLOR_MAGENTA, COLOR_WHITE);7727static_assert(LastColorPairIndex == 18, "Color indexes do not match.");77287729define_key("\033[Z", KEY_SHIFT_TAB);7730define_key("\033\015", KEY_ALT_ENTER);7731}7732}77337734void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); }77357736void IOHandlerCursesGUI::Run() {7737m_app_ap->Run(m_debugger);7738SetIsDone(true);7739}77407741IOHandlerCursesGUI::~IOHandlerCursesGUI() = default;77427743void IOHandlerCursesGUI::Cancel() {}77447745bool IOHandlerCursesGUI::Interrupt() {7746return m_debugger.GetCommandInterpreter().IOHandlerInterrupt(*this);7747}77487749void IOHandlerCursesGUI::GotEOF() {}77507751void IOHandlerCursesGUI::TerminalSizeChanged() {7752m_app_ap->TerminalSizeChanged();7753}77547755#endif // LLDB_ENABLE_CURSES775677577758