diff options
Diffstat (limited to 'contrib/llvm-project/lldb/source/Core/IOHandlerCursesGUI.cpp')
-rw-r--r-- | contrib/llvm-project/lldb/source/Core/IOHandlerCursesGUI.cpp | 7756 |
1 files changed, 7756 insertions, 0 deletions
diff --git a/contrib/llvm-project/lldb/source/Core/IOHandlerCursesGUI.cpp b/contrib/llvm-project/lldb/source/Core/IOHandlerCursesGUI.cpp new file mode 100644 index 000000000000..d922d32f9105 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/IOHandlerCursesGUI.cpp @@ -0,0 +1,7756 @@ +//===-- IOHandlerCursesGUI.cpp --------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Core/IOHandlerCursesGUI.h" +#include "lldb/Host/Config.h" + +#if LLDB_ENABLE_CURSES +#if CURSES_HAVE_NCURSES_CURSES_H +#include <ncurses/curses.h> +#include <ncurses/panel.h> +#else +#include <curses.h> +#include <panel.h> +#endif +#endif + +#if defined(__APPLE__) +#include <deque> +#endif +#include <string> + +#include "lldb/Core/Debugger.h" +#include "lldb/Core/ValueObjectUpdater.h" +#include "lldb/Host/File.h" +#include "lldb/Utility/AnsiTerminal.h" +#include "lldb/Utility/Predicate.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/Utility/StringList.h" +#include "lldb/lldb-forward.h" + +#include "lldb/Interpreter/CommandCompletions.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/OptionGroupPlatform.h" + +#if LLDB_ENABLE_CURSES +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Core/ValueObjectRegister.h" +#include "lldb/Symbol/Block.h" +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/State.h" +#endif + +#include "llvm/ADT/StringRef.h" + +#ifdef _WIN32 +#include "lldb/Host/windows/windows.h" +#endif + +#include <memory> +#include <mutex> + +#include <cassert> +#include <cctype> +#include <cerrno> +#include <cstdint> +#include <cstdio> +#include <cstring> +#include <functional> +#include <optional> +#include <type_traits> + +using namespace lldb; +using namespace lldb_private; +using llvm::StringRef; + +// we may want curses to be disabled for some builds for instance, windows +#if LLDB_ENABLE_CURSES + +#define KEY_CTRL_A 1 +#define KEY_CTRL_E 5 +#define KEY_CTRL_K 11 +#define KEY_RETURN 10 +#define KEY_ESCAPE 27 +#define KEY_DELETE 127 + +#define KEY_SHIFT_TAB (KEY_MAX + 1) +#define KEY_ALT_ENTER (KEY_MAX + 2) + +namespace curses { +class Menu; +class MenuDelegate; +class Window; +class WindowDelegate; +typedef std::shared_ptr<Menu> MenuSP; +typedef std::shared_ptr<MenuDelegate> MenuDelegateSP; +typedef std::shared_ptr<Window> WindowSP; +typedef std::shared_ptr<WindowDelegate> WindowDelegateSP; +typedef std::vector<MenuSP> Menus; +typedef std::vector<WindowSP> Windows; +typedef std::vector<WindowDelegateSP> WindowDelegates; + +#if 0 +type summary add -s "x=${var.x}, y=${var.y}" curses::Point +type summary add -s "w=${var.width}, h=${var.height}" curses::Size +type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect +#endif + +struct Point { + int x; + int y; + + Point(int _x = 0, int _y = 0) : x(_x), y(_y) {} + + void Clear() { + x = 0; + y = 0; + } + + Point &operator+=(const Point &rhs) { + x += rhs.x; + y += rhs.y; + return *this; + } + + void Dump() { printf("(x=%i, y=%i)\n", x, y); } +}; + +bool operator==(const Point &lhs, const Point &rhs) { + return lhs.x == rhs.x && lhs.y == rhs.y; +} + +bool operator!=(const Point &lhs, const Point &rhs) { + return lhs.x != rhs.x || lhs.y != rhs.y; +} + +struct Size { + int width; + int height; + Size(int w = 0, int h = 0) : width(w), height(h) {} + + void Clear() { + width = 0; + height = 0; + } + + void Dump() { printf("(w=%i, h=%i)\n", width, height); } +}; + +bool operator==(const Size &lhs, const Size &rhs) { + return lhs.width == rhs.width && lhs.height == rhs.height; +} + +bool operator!=(const Size &lhs, const Size &rhs) { + return lhs.width != rhs.width || lhs.height != rhs.height; +} + +struct Rect { + Point origin; + Size size; + + Rect() : origin(), size() {} + + Rect(const Point &p, const Size &s) : origin(p), size(s) {} + + void Clear() { + origin.Clear(); + size.Clear(); + } + + void Dump() { + printf("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width, + size.height); + } + + void Inset(int w, int h) { + if (size.width > w * 2) + size.width -= w * 2; + origin.x += w; + + if (size.height > h * 2) + size.height -= h * 2; + origin.y += h; + } + + // Return a status bar rectangle which is the last line of this rectangle. + // This rectangle will be modified to not include the status bar area. + Rect MakeStatusBar() { + Rect status_bar; + if (size.height > 1) { + status_bar.origin.x = origin.x; + status_bar.origin.y = size.height; + status_bar.size.width = size.width; + status_bar.size.height = 1; + --size.height; + } + return status_bar; + } + + // Return a menubar rectangle which is the first line of this rectangle. This + // rectangle will be modified to not include the menubar area. + Rect MakeMenuBar() { + Rect menubar; + if (size.height > 1) { + menubar.origin.x = origin.x; + menubar.origin.y = origin.y; + menubar.size.width = size.width; + menubar.size.height = 1; + ++origin.y; + --size.height; + } + return menubar; + } + + void HorizontalSplitPercentage(float top_percentage, Rect &top, + Rect &bottom) const { + float top_height = top_percentage * size.height; + HorizontalSplit(top_height, top, bottom); + } + + void HorizontalSplit(int top_height, Rect &top, Rect &bottom) const { + top = *this; + if (top_height < size.height) { + top.size.height = top_height; + bottom.origin.x = origin.x; + bottom.origin.y = origin.y + top.size.height; + bottom.size.width = size.width; + bottom.size.height = size.height - top.size.height; + } else { + bottom.Clear(); + } + } + + void VerticalSplitPercentage(float left_percentage, Rect &left, + Rect &right) const { + float left_width = left_percentage * size.width; + VerticalSplit(left_width, left, right); + } + + void VerticalSplit(int left_width, Rect &left, Rect &right) const { + left = *this; + if (left_width < size.width) { + left.size.width = left_width; + right.origin.x = origin.x + left.size.width; + right.origin.y = origin.y; + right.size.width = size.width - left.size.width; + right.size.height = size.height; + } else { + right.Clear(); + } + } +}; + +bool operator==(const Rect &lhs, const Rect &rhs) { + return lhs.origin == rhs.origin && lhs.size == rhs.size; +} + +bool operator!=(const Rect &lhs, const Rect &rhs) { + return lhs.origin != rhs.origin || lhs.size != rhs.size; +} + +enum HandleCharResult { + eKeyNotHandled = 0, + eKeyHandled = 1, + eQuitApplication = 2 +}; + +enum class MenuActionResult { + Handled, + NotHandled, + Quit // Exit all menus and quit +}; + +struct KeyHelp { + int ch; + const char *description; +}; + +// COLOR_PAIR index names +enum { + // First 16 colors are 8 black background and 8 blue background colors, + // needed by OutputColoredStringTruncated(). + BlackOnBlack = 1, + RedOnBlack, + GreenOnBlack, + YellowOnBlack, + BlueOnBlack, + MagentaOnBlack, + CyanOnBlack, + WhiteOnBlack, + BlackOnBlue, + RedOnBlue, + GreenOnBlue, + YellowOnBlue, + BlueOnBlue, + MagentaOnBlue, + CyanOnBlue, + WhiteOnBlue, + // Other colors, as needed. + BlackOnWhite, + MagentaOnWhite, + LastColorPairIndex = MagentaOnWhite +}; + +class WindowDelegate { +public: + virtual ~WindowDelegate() = default; + + virtual bool WindowDelegateDraw(Window &window, bool force) { + return false; // Drawing not handled + } + + virtual HandleCharResult WindowDelegateHandleChar(Window &window, int key) { + return eKeyNotHandled; + } + + virtual const char *WindowDelegateGetHelpText() { return nullptr; } + + virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; } +}; + +class HelpDialogDelegate : public WindowDelegate { +public: + HelpDialogDelegate(const char *text, KeyHelp *key_help_array); + + ~HelpDialogDelegate() override; + + bool WindowDelegateDraw(Window &window, bool force) override; + + HandleCharResult WindowDelegateHandleChar(Window &window, int key) override; + + size_t GetNumLines() const { return m_text.GetSize(); } + + size_t GetMaxLineLength() const { return m_text.GetMaxStringLength(); } + +protected: + StringList m_text; + int m_first_visible_line = 0; +}; + +// A surface is an abstraction for something than can be drawn on. The surface +// have a width, a height, a cursor position, and a multitude of drawing +// operations. This type should be sub-classed to get an actually useful ncurses +// object, such as a Window or a Pad. +class Surface { +public: + enum class Type { Window, Pad }; + + Surface(Surface::Type type) : m_type(type) {} + + WINDOW *get() { return m_window; } + + operator WINDOW *() { return m_window; } + + Surface SubSurface(Rect bounds) { + Surface subSurface(m_type); + if (m_type == Type::Pad) + subSurface.m_window = + ::subpad(m_window, bounds.size.height, bounds.size.width, + bounds.origin.y, bounds.origin.x); + else + subSurface.m_window = + ::derwin(m_window, bounds.size.height, bounds.size.width, + bounds.origin.y, bounds.origin.x); + return subSurface; + } + + // Copy a region of the surface to another surface. + void CopyToSurface(Surface &target, Point source_origin, Point target_origin, + Size size) { + ::copywin(m_window, target.get(), source_origin.y, source_origin.x, + target_origin.y, target_origin.x, + target_origin.y + size.height - 1, + target_origin.x + size.width - 1, false); + } + + int GetCursorX() const { return getcurx(m_window); } + int GetCursorY() const { return getcury(m_window); } + void MoveCursor(int x, int y) { ::wmove(m_window, y, x); } + + void AttributeOn(attr_t attr) { ::wattron(m_window, attr); } + void AttributeOff(attr_t attr) { ::wattroff(m_window, attr); } + + int GetMaxX() const { return getmaxx(m_window); } + int GetMaxY() const { return getmaxy(m_window); } + int GetWidth() const { return GetMaxX(); } + int GetHeight() const { return GetMaxY(); } + Size GetSize() const { return Size(GetWidth(), GetHeight()); } + // Get a zero origin rectangle width the surface size. + Rect GetFrame() const { return Rect(Point(), GetSize()); } + + void Clear() { ::wclear(m_window); } + void Erase() { ::werase(m_window); } + + void SetBackground(int color_pair_idx) { + ::wbkgd(m_window, COLOR_PAIR(color_pair_idx)); + } + + void PutChar(int ch) { ::waddch(m_window, ch); } + void PutCString(const char *s, int len = -1) { ::waddnstr(m_window, s, len); } + + void PutCStringTruncated(int right_pad, const char *s, int len = -1) { + int bytes_left = GetWidth() - GetCursorX(); + if (bytes_left > right_pad) { + bytes_left -= right_pad; + ::waddnstr(m_window, s, len < 0 ? bytes_left : std::min(bytes_left, len)); + } + } + + void Printf(const char *format, ...) __attribute__((format(printf, 2, 3))) { + va_list args; + va_start(args, format); + vw_printw(m_window, format, args); + va_end(args); + } + + void PrintfTruncated(int right_pad, const char *format, ...) + __attribute__((format(printf, 3, 4))) { + va_list args; + va_start(args, format); + StreamString strm; + strm.PrintfVarArg(format, args); + va_end(args); + PutCStringTruncated(right_pad, strm.GetData()); + } + + void VerticalLine(int n, chtype v_char = ACS_VLINE) { + ::wvline(m_window, v_char, n); + } + void HorizontalLine(int n, chtype h_char = ACS_HLINE) { + ::whline(m_window, h_char, n); + } + void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) { + ::box(m_window, v_char, h_char); + } + + void TitledBox(const char *title, chtype v_char = ACS_VLINE, + chtype h_char = ACS_HLINE) { + Box(v_char, h_char); + int title_offset = 2; + MoveCursor(title_offset, 0); + PutChar('['); + PutCString(title, GetWidth() - title_offset); + PutChar(']'); + } + + void Box(const Rect &bounds, chtype v_char = ACS_VLINE, + chtype h_char = ACS_HLINE) { + MoveCursor(bounds.origin.x, bounds.origin.y); + VerticalLine(bounds.size.height); + HorizontalLine(bounds.size.width); + PutChar(ACS_ULCORNER); + + MoveCursor(bounds.origin.x + bounds.size.width - 1, bounds.origin.y); + VerticalLine(bounds.size.height); + PutChar(ACS_URCORNER); + + MoveCursor(bounds.origin.x, bounds.origin.y + bounds.size.height - 1); + HorizontalLine(bounds.size.width); + PutChar(ACS_LLCORNER); + + MoveCursor(bounds.origin.x + bounds.size.width - 1, + bounds.origin.y + bounds.size.height - 1); + PutChar(ACS_LRCORNER); + } + + void TitledBox(const Rect &bounds, const char *title, + chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) { + Box(bounds, v_char, h_char); + int title_offset = 2; + MoveCursor(bounds.origin.x + title_offset, bounds.origin.y); + PutChar('['); + PutCString(title, bounds.size.width - title_offset); + PutChar(']'); + } + + // Curses doesn't allow direct output of color escape sequences, but that's + // how we get source lines from the Highligher class. Read the line and + // convert color escape sequences to curses color attributes. Use + // first_skip_count to skip leading visible characters. Returns false if all + // visible characters were skipped due to first_skip_count. + bool OutputColoredStringTruncated(int right_pad, StringRef string, + size_t skip_first_count, + bool use_blue_background) { + attr_t saved_attr; + short saved_pair; + bool result = false; + wattr_get(m_window, &saved_attr, &saved_pair, nullptr); + if (use_blue_background) + ::wattron(m_window, COLOR_PAIR(WhiteOnBlue)); + while (!string.empty()) { + size_t esc_pos = string.find(ANSI_ESC_START); + if (esc_pos == StringRef::npos) { + string = string.substr(skip_first_count); + if (!string.empty()) { + PutCStringTruncated(right_pad, string.data(), string.size()); + result = true; + } + break; + } + if (esc_pos > 0) { + if (skip_first_count > 0) { + int skip = std::min(esc_pos, skip_first_count); + string = string.substr(skip); + skip_first_count -= skip; + esc_pos -= skip; + } + if (esc_pos > 0) { + PutCStringTruncated(right_pad, string.data(), esc_pos); + result = true; + string = string.drop_front(esc_pos); + } + } + bool consumed = string.consume_front(ANSI_ESC_START); + assert(consumed); + UNUSED_IF_ASSERT_DISABLED(consumed); + // This is written to match our Highlighter classes, which seem to + // generate only foreground color escape sequences. If necessary, this + // will need to be extended. + // Only 8 basic foreground colors, underline and reset, our Highlighter + // doesn't use anything else. + int value; + if (!!string.consumeInteger(10, value) || // Returns false on success. + !(value == 0 || value == ANSI_CTRL_UNDERLINE || + (value >= ANSI_FG_COLOR_BLACK && value <= ANSI_FG_COLOR_WHITE))) { + llvm::errs() << "No valid color code in color escape sequence.\n"; + continue; + } + if (!string.consume_front(ANSI_ESC_END)) { + llvm::errs() << "Missing '" << ANSI_ESC_END + << "' in color escape sequence.\n"; + continue; + } + if (value == 0) { // Reset. + wattr_set(m_window, saved_attr, saved_pair, nullptr); + if (use_blue_background) + ::wattron(m_window, COLOR_PAIR(WhiteOnBlue)); + } else if (value == ANSI_CTRL_UNDERLINE) { + ::wattron(m_window, A_UNDERLINE); + } else { + // Mapped directly to first 16 color pairs (black/blue background). + ::wattron(m_window, COLOR_PAIR(value - ANSI_FG_COLOR_BLACK + 1 + + (use_blue_background ? 8 : 0))); + } + } + wattr_set(m_window, saved_attr, saved_pair, nullptr); + return result; + } + +protected: + Type m_type; + WINDOW *m_window = nullptr; +}; + +class Pad : public Surface { +public: + Pad(Size size) : Surface(Surface::Type::Pad) { + m_window = ::newpad(size.height, size.width); + } + + ~Pad() { ::delwin(m_window); } +}; + +class Window : public Surface { +public: + Window(const char *name) + : Surface(Surface::Type::Window), m_name(name), m_panel(nullptr), + m_parent(nullptr), m_subwindows(), m_delegate_sp(), + m_curr_active_window_idx(UINT32_MAX), + m_prev_active_window_idx(UINT32_MAX), m_delete(false), + m_needs_update(true), m_can_activate(true), m_is_subwin(false) {} + + Window(const char *name, WINDOW *w, bool del = true) + : Surface(Surface::Type::Window), m_name(name), m_panel(nullptr), + m_parent(nullptr), m_subwindows(), m_delegate_sp(), + m_curr_active_window_idx(UINT32_MAX), + m_prev_active_window_idx(UINT32_MAX), m_delete(del), + m_needs_update(true), m_can_activate(true), m_is_subwin(false) { + if (w) + Reset(w); + } + + Window(const char *name, const Rect &bounds) + : Surface(Surface::Type::Window), m_name(name), m_panel(nullptr), + m_parent(nullptr), m_subwindows(), m_delegate_sp(), + m_curr_active_window_idx(UINT32_MAX), + m_prev_active_window_idx(UINT32_MAX), m_delete(false), + m_needs_update(true), m_can_activate(true), m_is_subwin(false) { + Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y, + bounds.origin.y)); + } + + virtual ~Window() { + RemoveSubWindows(); + Reset(); + } + + void Reset(WINDOW *w = nullptr, bool del = true) { + if (m_window == w) + return; + + if (m_panel) { + ::del_panel(m_panel); + m_panel = nullptr; + } + if (m_window && m_delete) { + ::delwin(m_window); + m_window = nullptr; + m_delete = false; + } + if (w) { + m_window = w; + m_panel = ::new_panel(m_window); + m_delete = del; + } + } + + // Get the rectangle in our parent window + Rect GetBounds() const { return Rect(GetParentOrigin(), GetSize()); } + + Rect GetCenteredRect(int width, int height) { + Size size = GetSize(); + width = std::min(size.width, width); + height = std::min(size.height, height); + int x = (size.width - width) / 2; + int y = (size.height - height) / 2; + return Rect(Point(x, y), Size(width, height)); + } + + int GetChar() { return ::wgetch(m_window); } + Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); } + int GetParentX() const { return getparx(m_window); } + int GetParentY() const { return getpary(m_window); } + void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); } + void Resize(int w, int h) { ::wresize(m_window, h, w); } + void Resize(const Size &size) { + ::wresize(m_window, size.height, size.width); + } + void MoveWindow(const Point &origin) { + const bool moving_window = origin != GetParentOrigin(); + if (m_is_subwin && moving_window) { + // Can't move subwindows, must delete and re-create + Size size = GetSize(); + Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y, + origin.x), + true); + } else { + ::mvwin(m_window, origin.y, origin.x); + } + } + + void SetBounds(const Rect &bounds) { + const bool moving_window = bounds.origin != GetParentOrigin(); + if (m_is_subwin && moving_window) { + // Can't move subwindows, must delete and re-create + Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width, + bounds.origin.y, bounds.origin.x), + true); + } else { + if (moving_window) + MoveWindow(bounds.origin); + Resize(bounds.size); + } + } + + void Touch() { + ::touchwin(m_window); + if (m_parent) + m_parent->Touch(); + } + + WindowSP CreateSubWindow(const char *name, const Rect &bounds, + bool make_active) { + auto get_window = [this, &bounds]() { + return m_window + ? ::subwin(m_window, bounds.size.height, bounds.size.width, + bounds.origin.y, bounds.origin.x) + : ::newwin(bounds.size.height, bounds.size.width, + bounds.origin.y, bounds.origin.x); + }; + WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true); + subwindow_sp->m_is_subwin = subwindow_sp.operator bool(); + subwindow_sp->m_parent = this; + if (make_active) { + m_prev_active_window_idx = m_curr_active_window_idx; + m_curr_active_window_idx = m_subwindows.size(); + } + m_subwindows.push_back(subwindow_sp); + ::top_panel(subwindow_sp->m_panel); + m_needs_update = true; + return subwindow_sp; + } + + bool RemoveSubWindow(Window *window) { + Windows::iterator pos, end = m_subwindows.end(); + size_t i = 0; + for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) { + if ((*pos).get() == window) { + if (m_prev_active_window_idx == i) + m_prev_active_window_idx = UINT32_MAX; + else if (m_prev_active_window_idx != UINT32_MAX && + m_prev_active_window_idx > i) + --m_prev_active_window_idx; + + if (m_curr_active_window_idx == i) + m_curr_active_window_idx = UINT32_MAX; + else if (m_curr_active_window_idx != UINT32_MAX && + m_curr_active_window_idx > i) + --m_curr_active_window_idx; + window->Erase(); + m_subwindows.erase(pos); + m_needs_update = true; + if (m_parent) + m_parent->Touch(); + else + ::touchwin(stdscr); + return true; + } + } + return false; + } + + WindowSP FindSubWindow(const char *name) { + Windows::iterator pos, end = m_subwindows.end(); + size_t i = 0; + for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) { + if ((*pos)->m_name == name) + return *pos; + } + return WindowSP(); + } + + void RemoveSubWindows() { + m_curr_active_window_idx = UINT32_MAX; + m_prev_active_window_idx = UINT32_MAX; + for (Windows::iterator pos = m_subwindows.begin(); + pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) { + (*pos)->Erase(); + } + if (m_parent) + m_parent->Touch(); + else + ::touchwin(stdscr); + } + + // Window drawing utilities + void DrawTitleBox(const char *title, const char *bottom_message = nullptr) { + attr_t attr = 0; + if (IsActive()) + attr = A_BOLD | COLOR_PAIR(BlackOnWhite); + else + attr = 0; + if (attr) + AttributeOn(attr); + + Box(); + MoveCursor(3, 0); + + if (title && title[0]) { + PutChar('<'); + PutCString(title); + PutChar('>'); + } + + if (bottom_message && bottom_message[0]) { + int bottom_message_length = strlen(bottom_message); + int x = GetWidth() - 3 - (bottom_message_length + 2); + + if (x > 0) { + MoveCursor(x, GetHeight() - 1); + PutChar('['); + PutCString(bottom_message); + PutChar(']'); + } else { + MoveCursor(1, GetHeight() - 1); + PutChar('['); + PutCStringTruncated(1, bottom_message); + } + } + if (attr) + AttributeOff(attr); + } + + virtual void Draw(bool force) { + if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force)) + return; + + for (auto &subwindow_sp : m_subwindows) + subwindow_sp->Draw(force); + } + + bool CreateHelpSubwindow() { + if (m_delegate_sp) { + const char *text = m_delegate_sp->WindowDelegateGetHelpText(); + KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp(); + if ((text && text[0]) || key_help) { + std::unique_ptr<HelpDialogDelegate> help_delegate_up( + new HelpDialogDelegate(text, key_help)); + const size_t num_lines = help_delegate_up->GetNumLines(); + const size_t max_length = help_delegate_up->GetMaxLineLength(); + Rect bounds = GetBounds(); + bounds.Inset(1, 1); + if (max_length + 4 < static_cast<size_t>(bounds.size.width)) { + bounds.origin.x += (bounds.size.width - max_length + 4) / 2; + bounds.size.width = max_length + 4; + } else { + if (bounds.size.width > 100) { + const int inset_w = bounds.size.width / 4; + bounds.origin.x += inset_w; + bounds.size.width -= 2 * inset_w; + } + } + + if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) { + bounds.origin.y += (bounds.size.height - num_lines + 2) / 2; + bounds.size.height = num_lines + 2; + } else { + if (bounds.size.height > 100) { + const int inset_h = bounds.size.height / 4; + bounds.origin.y += inset_h; + bounds.size.height -= 2 * inset_h; + } + } + WindowSP help_window_sp; + Window *parent_window = GetParent(); + if (parent_window) + help_window_sp = parent_window->CreateSubWindow("Help", bounds, true); + else + help_window_sp = CreateSubWindow("Help", bounds, true); + help_window_sp->SetDelegate( + WindowDelegateSP(help_delegate_up.release())); + return true; + } + } + return false; + } + + virtual HandleCharResult HandleChar(int key) { + // Always check the active window first + HandleCharResult result = eKeyNotHandled; + WindowSP active_window_sp = GetActiveWindow(); + if (active_window_sp) { + result = active_window_sp->HandleChar(key); + if (result != eKeyNotHandled) + return result; + } + + if (m_delegate_sp) { + result = m_delegate_sp->WindowDelegateHandleChar(*this, key); + if (result != eKeyNotHandled) + return result; + } + + // Then check for any windows that want any keys that weren't handled. This + // is typically only for a menubar. Make a copy of the subwindows in case + // any HandleChar() functions muck with the subwindows. If we don't do + // this, we can crash when iterating over the subwindows. + Windows subwindows(m_subwindows); + for (auto subwindow_sp : subwindows) { + if (!subwindow_sp->m_can_activate) { + HandleCharResult result = subwindow_sp->HandleChar(key); + if (result != eKeyNotHandled) + return result; + } + } + + return eKeyNotHandled; + } + + WindowSP GetActiveWindow() { + if (!m_subwindows.empty()) { + if (m_curr_active_window_idx >= m_subwindows.size()) { + if (m_prev_active_window_idx < m_subwindows.size()) { + m_curr_active_window_idx = m_prev_active_window_idx; + m_prev_active_window_idx = UINT32_MAX; + } else if (IsActive()) { + m_prev_active_window_idx = UINT32_MAX; + m_curr_active_window_idx = UINT32_MAX; + + // Find first window that wants to be active if this window is active + const size_t num_subwindows = m_subwindows.size(); + for (size_t i = 0; i < num_subwindows; ++i) { + if (m_subwindows[i]->GetCanBeActive()) { + m_curr_active_window_idx = i; + break; + } + } + } + } + + if (m_curr_active_window_idx < m_subwindows.size()) + return m_subwindows[m_curr_active_window_idx]; + } + return WindowSP(); + } + + bool GetCanBeActive() const { return m_can_activate; } + + void SetCanBeActive(bool b) { m_can_activate = b; } + + void SetDelegate(const WindowDelegateSP &delegate_sp) { + m_delegate_sp = delegate_sp; + } + + Window *GetParent() const { return m_parent; } + + bool IsActive() const { + if (m_parent) + return m_parent->GetActiveWindow().get() == this; + else + return true; // Top level window is always active + } + + void SelectNextWindowAsActive() { + // Move active focus to next window + const int num_subwindows = m_subwindows.size(); + int start_idx = 0; + if (m_curr_active_window_idx != UINT32_MAX) { + m_prev_active_window_idx = m_curr_active_window_idx; + start_idx = m_curr_active_window_idx + 1; + } + for (int idx = start_idx; idx < num_subwindows; ++idx) { + if (m_subwindows[idx]->GetCanBeActive()) { + m_curr_active_window_idx = idx; + return; + } + } + for (int idx = 0; idx < start_idx; ++idx) { + if (m_subwindows[idx]->GetCanBeActive()) { + m_curr_active_window_idx = idx; + break; + } + } + } + + void SelectPreviousWindowAsActive() { + // Move active focus to previous window + const int num_subwindows = m_subwindows.size(); + int start_idx = num_subwindows - 1; + if (m_curr_active_window_idx != UINT32_MAX) { + m_prev_active_window_idx = m_curr_active_window_idx; + start_idx = m_curr_active_window_idx - 1; + } + for (int idx = start_idx; idx >= 0; --idx) { + if (m_subwindows[idx]->GetCanBeActive()) { + m_curr_active_window_idx = idx; + return; + } + } + for (int idx = num_subwindows - 1; idx > start_idx; --idx) { + if (m_subwindows[idx]->GetCanBeActive()) { + m_curr_active_window_idx = idx; + break; + } + } + } + + const char *GetName() const { return m_name.c_str(); } + +protected: + std::string m_name; + PANEL *m_panel; + Window *m_parent; + Windows m_subwindows; + WindowDelegateSP m_delegate_sp; + uint32_t m_curr_active_window_idx; + uint32_t m_prev_active_window_idx; + bool m_delete; + bool m_needs_update; + bool m_can_activate; + bool m_is_subwin; + +private: + Window(const Window &) = delete; + const Window &operator=(const Window &) = delete; +}; + +///////// +// Forms +///////// + +// A scroll context defines a vertical region that needs to be visible in a +// scrolling area. The region is defined by the index of the start and end lines +// of the region. The start and end lines may be equal, in which case, the +// region is a single line. +struct ScrollContext { + int start; + int end; + + ScrollContext(int line) : start(line), end(line) {} + ScrollContext(int _start, int _end) : start(_start), end(_end) {} + + void Offset(int offset) { + start += offset; + end += offset; + } +}; + +class FieldDelegate { +public: + virtual ~FieldDelegate() = default; + + // Returns the number of lines needed to draw the field. The draw method will + // be given a surface that have exactly this number of lines. + virtual int FieldDelegateGetHeight() = 0; + + // Returns the scroll context in the local coordinates of the field. By + // default, the scroll context spans the whole field. Bigger fields with + // internal navigation should override this method to provide a finer context. + // Typical override methods would first get the scroll context of the internal + // element then add the offset of the element in the field. + virtual ScrollContext FieldDelegateGetScrollContext() { + return ScrollContext(0, FieldDelegateGetHeight() - 1); + } + + // Draw the field in the given subpad surface. The surface have a height that + // is equal to the height returned by FieldDelegateGetHeight(). If the field + // is selected in the form window, then is_selected will be true. + virtual void FieldDelegateDraw(Surface &surface, bool is_selected) = 0; + + // Handle the key that wasn't handled by the form window or a container field. + virtual HandleCharResult FieldDelegateHandleChar(int key) { + return eKeyNotHandled; + } + + // This is executed once the user exists the field, that is, once the user + // navigates to the next or the previous field. This is particularly useful to + // do in-field validation and error setting. Fields with internal navigation + // should call this method on their fields. + virtual void FieldDelegateExitCallback() {} + + // Fields may have internal navigation, for instance, a List Field have + // multiple internal elements, which needs to be navigated. To allow for this + // mechanism, the window shouldn't handle the navigation keys all the time, + // and instead call the key handing method of the selected field. It should + // only handle the navigation keys when the field contains a single element or + // have the last or first element selected depending on if the user is + // navigating forward or backward. Additionally, once a field is selected in + // the forward or backward direction, its first or last internal element + // should be selected. The following methods implements those mechanisms. + + // Returns true if the first element in the field is selected or if the field + // contains a single element. + virtual bool FieldDelegateOnFirstOrOnlyElement() { return true; } + + // Returns true if the last element in the field is selected or if the field + // contains a single element. + virtual bool FieldDelegateOnLastOrOnlyElement() { return true; } + + // Select the first element in the field if multiple elements exists. + virtual void FieldDelegateSelectFirstElement() {} + + // Select the last element in the field if multiple elements exists. + virtual void FieldDelegateSelectLastElement() {} + + // Returns true if the field has an error, false otherwise. + virtual bool FieldDelegateHasError() { return false; } + + bool FieldDelegateIsVisible() { return m_is_visible; } + + void FieldDelegateHide() { m_is_visible = false; } + + void FieldDelegateShow() { m_is_visible = true; } + +protected: + bool m_is_visible = true; +}; + +typedef std::unique_ptr<FieldDelegate> FieldDelegateUP; + +class TextFieldDelegate : public FieldDelegate { +public: + TextFieldDelegate(const char *label, const char *content, bool required) + : m_label(label), m_required(required) { + if (content) + m_content = content; + } + + // Text fields are drawn as titled boxes of a single line, with a possible + // error messages at the end. + // + // __[Label]___________ + // | | + // |__________________| + // - Error message if it exists. + + // The text field has a height of 3 lines. 2 lines for borders and 1 line for + // the content. + int GetFieldHeight() { return 3; } + + // The text field has a full height of 3 or 4 lines. 3 lines for the actual + // field and an optional line for an error if it exists. + int FieldDelegateGetHeight() override { + int height = GetFieldHeight(); + if (FieldDelegateHasError()) + height++; + return height; + } + + // Get the cursor X position in the surface coordinate. + int GetCursorXPosition() { return m_cursor_position - m_first_visibile_char; } + + int GetContentLength() { return m_content.length(); } + + void DrawContent(Surface &surface, bool is_selected) { + UpdateScrolling(surface.GetWidth()); + + surface.MoveCursor(0, 0); + const char *text = m_content.c_str() + m_first_visibile_char; + surface.PutCString(text, surface.GetWidth()); + + // Highlight the cursor. + surface.MoveCursor(GetCursorXPosition(), 0); + if (is_selected) + surface.AttributeOn(A_REVERSE); + if (m_cursor_position == GetContentLength()) + // Cursor is past the last character. Highlight an empty space. + surface.PutChar(' '); + else + surface.PutChar(m_content[m_cursor_position]); + if (is_selected) + surface.AttributeOff(A_REVERSE); + } + + void DrawField(Surface &surface, bool is_selected) { + surface.TitledBox(m_label.c_str()); + + Rect content_bounds = surface.GetFrame(); + content_bounds.Inset(1, 1); + Surface content_surface = surface.SubSurface(content_bounds); + + DrawContent(content_surface, is_selected); + } + + void DrawError(Surface &surface) { + if (!FieldDelegateHasError()) + return; + surface.MoveCursor(0, 0); + surface.AttributeOn(COLOR_PAIR(RedOnBlack)); + surface.PutChar(ACS_DIAMOND); + surface.PutChar(' '); + surface.PutCStringTruncated(1, GetError().c_str()); + surface.AttributeOff(COLOR_PAIR(RedOnBlack)); + } + + void FieldDelegateDraw(Surface &surface, bool is_selected) override { + Rect frame = surface.GetFrame(); + Rect field_bounds, error_bounds; + frame.HorizontalSplit(GetFieldHeight(), field_bounds, error_bounds); + Surface field_surface = surface.SubSurface(field_bounds); + Surface error_surface = surface.SubSurface(error_bounds); + + DrawField(field_surface, is_selected); + DrawError(error_surface); + } + + // Get the position of the last visible character. + int GetLastVisibleCharPosition(int width) { + int position = m_first_visibile_char + width - 1; + return std::min(position, GetContentLength()); + } + + void UpdateScrolling(int width) { + if (m_cursor_position < m_first_visibile_char) { + m_first_visibile_char = m_cursor_position; + return; + } + + if (m_cursor_position > GetLastVisibleCharPosition(width)) + m_first_visibile_char = m_cursor_position - (width - 1); + } + + // The cursor is allowed to move one character past the string. + // m_cursor_position is in range [0, GetContentLength()]. + void MoveCursorRight() { + if (m_cursor_position < GetContentLength()) + m_cursor_position++; + } + + void MoveCursorLeft() { + if (m_cursor_position > 0) + m_cursor_position--; + } + + void MoveCursorToStart() { m_cursor_position = 0; } + + void MoveCursorToEnd() { m_cursor_position = GetContentLength(); } + + void ScrollLeft() { + if (m_first_visibile_char > 0) + m_first_visibile_char--; + } + + // Insert a character at the current cursor position and advance the cursor + // position. + void InsertChar(char character) { + m_content.insert(m_cursor_position, 1, character); + m_cursor_position++; + ClearError(); + } + + // Remove the character before the cursor position, retreat the cursor + // position, and scroll left. + void RemovePreviousChar() { + if (m_cursor_position == 0) + return; + + m_content.erase(m_cursor_position - 1, 1); + m_cursor_position--; + ScrollLeft(); + ClearError(); + } + + // Remove the character after the cursor position. + void RemoveNextChar() { + if (m_cursor_position == GetContentLength()) + return; + + m_content.erase(m_cursor_position, 1); + ClearError(); + } + + // Clear characters from the current cursor position to the end. + void ClearToEnd() { + m_content.erase(m_cursor_position); + ClearError(); + } + + void Clear() { + m_content.clear(); + m_cursor_position = 0; + ClearError(); + } + + // True if the key represents a char that can be inserted in the field + // content, false otherwise. + virtual bool IsAcceptableChar(int key) { + // The behavior of isprint is undefined when the value is not representable + // as an unsigned char. So explicitly check for non-ascii key codes. + if (key > 127) + return false; + return isprint(key); + } + + HandleCharResult FieldDelegateHandleChar(int key) override { + if (IsAcceptableChar(key)) { + ClearError(); + InsertChar((char)key); + return eKeyHandled; + } + + switch (key) { + case KEY_HOME: + case KEY_CTRL_A: + MoveCursorToStart(); + return eKeyHandled; + case KEY_END: + case KEY_CTRL_E: + MoveCursorToEnd(); + return eKeyHandled; + case KEY_RIGHT: + case KEY_SF: + MoveCursorRight(); + return eKeyHandled; + case KEY_LEFT: + case KEY_SR: + MoveCursorLeft(); + return eKeyHandled; + case KEY_BACKSPACE: + case KEY_DELETE: + RemovePreviousChar(); + return eKeyHandled; + case KEY_DC: + RemoveNextChar(); + return eKeyHandled; + case KEY_EOL: + case KEY_CTRL_K: + ClearToEnd(); + return eKeyHandled; + case KEY_DL: + case KEY_CLEAR: + Clear(); + return eKeyHandled; + default: + break; + } + return eKeyNotHandled; + } + + bool FieldDelegateHasError() override { return !m_error.empty(); } + + void FieldDelegateExitCallback() override { + if (!IsSpecified() && m_required) + SetError("This field is required!"); + } + + bool IsSpecified() { return !m_content.empty(); } + + void ClearError() { m_error.clear(); } + + const std::string &GetError() { return m_error; } + + void SetError(const char *error) { m_error = error; } + + const std::string &GetText() { return m_content; } + + void SetText(const char *text) { + if (text == nullptr) { + m_content.clear(); + return; + } + m_content = text; + } + +protected: + std::string m_label; + bool m_required; + // The position of the top left corner character of the border. + std::string m_content; + // The cursor position in the content string itself. Can be in the range + // [0, GetContentLength()]. + int m_cursor_position = 0; + // The index of the first visible character in the content. + int m_first_visibile_char = 0; + // Optional error message. If empty, field is considered to have no error. + std::string m_error; +}; + +class IntegerFieldDelegate : public TextFieldDelegate { +public: + IntegerFieldDelegate(const char *label, int content, bool required) + : TextFieldDelegate(label, std::to_string(content).c_str(), required) {} + + // Only accept digits. + bool IsAcceptableChar(int key) override { return isdigit(key); } + + // Returns the integer content of the field. + int GetInteger() { return std::stoi(m_content); } +}; + +class FileFieldDelegate : public TextFieldDelegate { +public: + FileFieldDelegate(const char *label, const char *content, bool need_to_exist, + bool required) + : TextFieldDelegate(label, content, required), + m_need_to_exist(need_to_exist) {} + + void FieldDelegateExitCallback() override { + TextFieldDelegate::FieldDelegateExitCallback(); + if (!IsSpecified()) + return; + + if (!m_need_to_exist) + return; + + FileSpec file = GetResolvedFileSpec(); + if (!FileSystem::Instance().Exists(file)) { + SetError("File doesn't exist!"); + return; + } + if (FileSystem::Instance().IsDirectory(file)) { + SetError("Not a file!"); + return; + } + } + + FileSpec GetFileSpec() { + FileSpec file_spec(GetPath()); + return file_spec; + } + + FileSpec GetResolvedFileSpec() { + FileSpec file_spec(GetPath()); + FileSystem::Instance().Resolve(file_spec); + return file_spec; + } + + const std::string &GetPath() { return m_content; } + +protected: + bool m_need_to_exist; +}; + +class DirectoryFieldDelegate : public TextFieldDelegate { +public: + DirectoryFieldDelegate(const char *label, const char *content, + bool need_to_exist, bool required) + : TextFieldDelegate(label, content, required), + m_need_to_exist(need_to_exist) {} + + void FieldDelegateExitCallback() override { + TextFieldDelegate::FieldDelegateExitCallback(); + if (!IsSpecified()) + return; + + if (!m_need_to_exist) + return; + + FileSpec file = GetResolvedFileSpec(); + if (!FileSystem::Instance().Exists(file)) { + SetError("Directory doesn't exist!"); + return; + } + if (!FileSystem::Instance().IsDirectory(file)) { + SetError("Not a directory!"); + return; + } + } + + FileSpec GetFileSpec() { + FileSpec file_spec(GetPath()); + return file_spec; + } + + FileSpec GetResolvedFileSpec() { + FileSpec file_spec(GetPath()); + FileSystem::Instance().Resolve(file_spec); + return file_spec; + } + + const std::string &GetPath() { return m_content; } + +protected: + bool m_need_to_exist; +}; + +class ArchFieldDelegate : public TextFieldDelegate { +public: + ArchFieldDelegate(const char *label, const char *content, bool required) + : TextFieldDelegate(label, content, required) {} + + void FieldDelegateExitCallback() override { + TextFieldDelegate::FieldDelegateExitCallback(); + if (!IsSpecified()) + return; + + if (!GetArchSpec().IsValid()) + SetError("Not a valid arch!"); + } + + const std::string &GetArchString() { return m_content; } + + ArchSpec GetArchSpec() { return ArchSpec(GetArchString()); } +}; + +class BooleanFieldDelegate : public FieldDelegate { +public: + BooleanFieldDelegate(const char *label, bool content) + : m_label(label), m_content(content) {} + + // Boolean fields are drawn as checkboxes. + // + // [X] Label or [ ] Label + + // Boolean fields are have a single line. + int FieldDelegateGetHeight() override { return 1; } + + void FieldDelegateDraw(Surface &surface, bool is_selected) override { + surface.MoveCursor(0, 0); + surface.PutChar('['); + if (is_selected) + surface.AttributeOn(A_REVERSE); + surface.PutChar(m_content ? ACS_DIAMOND : ' '); + if (is_selected) + surface.AttributeOff(A_REVERSE); + surface.PutChar(']'); + surface.PutChar(' '); + surface.PutCString(m_label.c_str()); + } + + void ToggleContent() { m_content = !m_content; } + + void SetContentToTrue() { m_content = true; } + + void SetContentToFalse() { m_content = false; } + + HandleCharResult FieldDelegateHandleChar(int key) override { + switch (key) { + case 't': + case '1': + SetContentToTrue(); + return eKeyHandled; + case 'f': + case '0': + SetContentToFalse(); + return eKeyHandled; + case ' ': + case '\r': + case '\n': + case KEY_ENTER: + ToggleContent(); + return eKeyHandled; + default: + break; + } + return eKeyNotHandled; + } + + // Returns the boolean content of the field. + bool GetBoolean() { return m_content; } + +protected: + std::string m_label; + bool m_content; +}; + +class ChoicesFieldDelegate : public FieldDelegate { +public: + ChoicesFieldDelegate(const char *label, int number_of_visible_choices, + std::vector<std::string> choices) + : m_label(label), m_number_of_visible_choices(number_of_visible_choices), + m_choices(choices) {} + + // Choices fields are drawn as titles boxses of a number of visible choices. + // The rest of the choices become visible as the user scroll. The selected + // choice is denoted by a diamond as the first character. + // + // __[Label]___________ + // |-Choice 1 | + // | Choice 2 | + // | Choice 3 | + // |__________________| + + // Choices field have two border characters plus the number of visible + // choices. + int FieldDelegateGetHeight() override { + return m_number_of_visible_choices + 2; + } + + int GetNumberOfChoices() { return m_choices.size(); } + + // Get the index of the last visible choice. + int GetLastVisibleChoice() { + int index = m_first_visibile_choice + m_number_of_visible_choices; + return std::min(index, GetNumberOfChoices()) - 1; + } + + void DrawContent(Surface &surface, bool is_selected) { + int choices_to_draw = GetLastVisibleChoice() - m_first_visibile_choice + 1; + for (int i = 0; i < choices_to_draw; i++) { + surface.MoveCursor(0, i); + int current_choice = m_first_visibile_choice + i; + const char *text = m_choices[current_choice].c_str(); + bool highlight = is_selected && current_choice == m_choice; + if (highlight) + surface.AttributeOn(A_REVERSE); + surface.PutChar(current_choice == m_choice ? ACS_DIAMOND : ' '); + surface.PutCString(text); + if (highlight) + surface.AttributeOff(A_REVERSE); + } + } + + void FieldDelegateDraw(Surface &surface, bool is_selected) override { + UpdateScrolling(); + + surface.TitledBox(m_label.c_str()); + + Rect content_bounds = surface.GetFrame(); + content_bounds.Inset(1, 1); + Surface content_surface = surface.SubSurface(content_bounds); + + DrawContent(content_surface, is_selected); + } + + void SelectPrevious() { + if (m_choice > 0) + m_choice--; + } + + void SelectNext() { + if (m_choice < GetNumberOfChoices() - 1) + m_choice++; + } + + void UpdateScrolling() { + if (m_choice > GetLastVisibleChoice()) { + m_first_visibile_choice = m_choice - (m_number_of_visible_choices - 1); + return; + } + + if (m_choice < m_first_visibile_choice) + m_first_visibile_choice = m_choice; + } + + HandleCharResult FieldDelegateHandleChar(int key) override { + switch (key) { + case KEY_UP: + SelectPrevious(); + return eKeyHandled; + case KEY_DOWN: + SelectNext(); + return eKeyHandled; + default: + break; + } + return eKeyNotHandled; + } + + // Returns the content of the choice as a string. + std::string GetChoiceContent() { return m_choices[m_choice]; } + + // Returns the index of the choice. + int GetChoice() { return m_choice; } + + void SetChoice(llvm::StringRef choice) { + for (int i = 0; i < GetNumberOfChoices(); i++) { + if (choice == m_choices[i]) { + m_choice = i; + return; + } + } + } + +protected: + std::string m_label; + int m_number_of_visible_choices; + std::vector<std::string> m_choices; + // The index of the selected choice. + int m_choice = 0; + // The index of the first visible choice in the field. + int m_first_visibile_choice = 0; +}; + +class PlatformPluginFieldDelegate : public ChoicesFieldDelegate { +public: + PlatformPluginFieldDelegate(Debugger &debugger) + : ChoicesFieldDelegate("Platform Plugin", 3, GetPossiblePluginNames()) { + PlatformSP platform_sp = debugger.GetPlatformList().GetSelectedPlatform(); + if (platform_sp) + SetChoice(platform_sp->GetPluginName()); + } + + std::vector<std::string> GetPossiblePluginNames() { + std::vector<std::string> names; + size_t i = 0; + for (llvm::StringRef name = + PluginManager::GetPlatformPluginNameAtIndex(i++); + !name.empty(); name = PluginManager::GetProcessPluginNameAtIndex(i++)) + names.push_back(name.str()); + return names; + } + + std::string GetPluginName() { + std::string plugin_name = GetChoiceContent(); + return plugin_name; + } +}; + +class ProcessPluginFieldDelegate : public ChoicesFieldDelegate { +public: + ProcessPluginFieldDelegate() + : ChoicesFieldDelegate("Process Plugin", 3, GetPossiblePluginNames()) {} + + std::vector<std::string> GetPossiblePluginNames() { + std::vector<std::string> names; + names.push_back("<default>"); + + size_t i = 0; + for (llvm::StringRef name = PluginManager::GetProcessPluginNameAtIndex(i++); + !name.empty(); name = PluginManager::GetProcessPluginNameAtIndex(i++)) + names.push_back(name.str()); + return names; + } + + std::string GetPluginName() { + std::string plugin_name = GetChoiceContent(); + if (plugin_name == "<default>") + return ""; + return plugin_name; + } +}; + +class LazyBooleanFieldDelegate : public ChoicesFieldDelegate { +public: + LazyBooleanFieldDelegate(const char *label, const char *calculate_label) + : ChoicesFieldDelegate(label, 3, GetPossibleOptions(calculate_label)) {} + + static constexpr const char *kNo = "No"; + static constexpr const char *kYes = "Yes"; + + std::vector<std::string> GetPossibleOptions(const char *calculate_label) { + std::vector<std::string> options; + options.push_back(calculate_label); + options.push_back(kYes); + options.push_back(kNo); + return options; + } + + LazyBool GetLazyBoolean() { + std::string choice = GetChoiceContent(); + if (choice == kNo) + return eLazyBoolNo; + else if (choice == kYes) + return eLazyBoolYes; + else + return eLazyBoolCalculate; + } +}; + +template <class T> class ListFieldDelegate : public FieldDelegate { +public: + ListFieldDelegate(const char *label, T default_field) + : m_label(label), m_default_field(default_field), + m_selection_type(SelectionType::NewButton) {} + + // Signify which element is selected. If a field or a remove button is + // selected, then m_selection_index signifies the particular field that + // is selected or the field that the remove button belongs to. + enum class SelectionType { Field, RemoveButton, NewButton }; + + // A List field is drawn as a titled box of a number of other fields of the + // same type. Each field has a Remove button next to it that removes the + // corresponding field. Finally, the last line contains a New button to add a + // new field. + // + // __[Label]___________ + // | Field 0 [Remove] | + // | Field 1 [Remove] | + // | Field 2 [Remove] | + // | [New] | + // |__________________| + + // List fields have two lines for border characters, 1 line for the New + // button, and the total height of the available fields. + int FieldDelegateGetHeight() override { + // 2 border characters. + int height = 2; + // Total height of the fields. + for (int i = 0; i < GetNumberOfFields(); i++) { + height += m_fields[i].FieldDelegateGetHeight(); + } + // A line for the New button. + height++; + return height; + } + + ScrollContext FieldDelegateGetScrollContext() override { + int height = FieldDelegateGetHeight(); + if (m_selection_type == SelectionType::NewButton) + return ScrollContext(height - 2, height - 1); + + FieldDelegate &field = m_fields[m_selection_index]; + ScrollContext context = field.FieldDelegateGetScrollContext(); + + // Start at 1 because of the top border. + int offset = 1; + for (int i = 0; i < m_selection_index; i++) { + offset += m_fields[i].FieldDelegateGetHeight(); + } + context.Offset(offset); + + // If the scroll context is touching the top border, include it in the + // context to show the label. + if (context.start == 1) + context.start--; + + // If the scroll context is touching the new button, include it as well as + // the bottom border in the context. + if (context.end == height - 3) + context.end += 2; + + return context; + } + + void DrawRemoveButton(Surface &surface, int highlight) { + surface.MoveCursor(1, surface.GetHeight() / 2); + if (highlight) + surface.AttributeOn(A_REVERSE); + surface.PutCString("[Remove]"); + if (highlight) + surface.AttributeOff(A_REVERSE); + } + + void DrawFields(Surface &surface, bool is_selected) { + int line = 0; + int width = surface.GetWidth(); + for (int i = 0; i < GetNumberOfFields(); i++) { + int height = m_fields[i].FieldDelegateGetHeight(); + Rect bounds = Rect(Point(0, line), Size(width, height)); + Rect field_bounds, remove_button_bounds; + bounds.VerticalSplit(bounds.size.width - sizeof(" [Remove]"), + field_bounds, remove_button_bounds); + Surface field_surface = surface.SubSurface(field_bounds); + Surface remove_button_surface = surface.SubSurface(remove_button_bounds); + + bool is_element_selected = m_selection_index == i && is_selected; + bool is_field_selected = + is_element_selected && m_selection_type == SelectionType::Field; + bool is_remove_button_selected = + is_element_selected && + m_selection_type == SelectionType::RemoveButton; + m_fields[i].FieldDelegateDraw(field_surface, is_field_selected); + DrawRemoveButton(remove_button_surface, is_remove_button_selected); + + line += height; + } + } + + void DrawNewButton(Surface &surface, bool is_selected) { + const char *button_text = "[New]"; + int x = (surface.GetWidth() - sizeof(button_text) - 1) / 2; + surface.MoveCursor(x, 0); + bool highlight = + is_selected && m_selection_type == SelectionType::NewButton; + if (highlight) + surface.AttributeOn(A_REVERSE); + surface.PutCString(button_text); + if (highlight) + surface.AttributeOff(A_REVERSE); + } + + void FieldDelegateDraw(Surface &surface, bool is_selected) override { + surface.TitledBox(m_label.c_str()); + + Rect content_bounds = surface.GetFrame(); + content_bounds.Inset(1, 1); + Rect fields_bounds, new_button_bounds; + content_bounds.HorizontalSplit(content_bounds.size.height - 1, + fields_bounds, new_button_bounds); + Surface fields_surface = surface.SubSurface(fields_bounds); + Surface new_button_surface = surface.SubSurface(new_button_bounds); + + DrawFields(fields_surface, is_selected); + DrawNewButton(new_button_surface, is_selected); + } + + void AddNewField() { + m_fields.push_back(m_default_field); + m_selection_index = GetNumberOfFields() - 1; + m_selection_type = SelectionType::Field; + FieldDelegate &field = m_fields[m_selection_index]; + field.FieldDelegateSelectFirstElement(); + } + + void RemoveField() { + m_fields.erase(m_fields.begin() + m_selection_index); + if (m_selection_index != 0) + m_selection_index--; + + if (GetNumberOfFields() > 0) { + m_selection_type = SelectionType::Field; + FieldDelegate &field = m_fields[m_selection_index]; + field.FieldDelegateSelectFirstElement(); + } else + m_selection_type = SelectionType::NewButton; + } + + HandleCharResult SelectNext(int key) { + if (m_selection_type == SelectionType::NewButton) + return eKeyNotHandled; + + if (m_selection_type == SelectionType::RemoveButton) { + if (m_selection_index == GetNumberOfFields() - 1) { + m_selection_type = SelectionType::NewButton; + return eKeyHandled; + } + m_selection_index++; + m_selection_type = SelectionType::Field; + FieldDelegate &next_field = m_fields[m_selection_index]; + next_field.FieldDelegateSelectFirstElement(); + return eKeyHandled; + } + + FieldDelegate &field = m_fields[m_selection_index]; + if (!field.FieldDelegateOnLastOrOnlyElement()) { + return field.FieldDelegateHandleChar(key); + } + + field.FieldDelegateExitCallback(); + + m_selection_type = SelectionType::RemoveButton; + return eKeyHandled; + } + + HandleCharResult SelectPrevious(int key) { + if (FieldDelegateOnFirstOrOnlyElement()) + return eKeyNotHandled; + + if (m_selection_type == SelectionType::RemoveButton) { + m_selection_type = SelectionType::Field; + FieldDelegate &field = m_fields[m_selection_index]; + field.FieldDelegateSelectLastElement(); + return eKeyHandled; + } + + if (m_selection_type == SelectionType::NewButton) { + m_selection_type = SelectionType::RemoveButton; + m_selection_index = GetNumberOfFields() - 1; + return eKeyHandled; + } + + FieldDelegate &field = m_fields[m_selection_index]; + if (!field.FieldDelegateOnFirstOrOnlyElement()) { + return field.FieldDelegateHandleChar(key); + } + + field.FieldDelegateExitCallback(); + + m_selection_type = SelectionType::RemoveButton; + m_selection_index--; + return eKeyHandled; + } + + // If the last element of the field is selected and it didn't handle the key. + // Select the next field or new button if the selected field is the last one. + HandleCharResult SelectNextInList(int key) { + assert(m_selection_type == SelectionType::Field); + + FieldDelegate &field = m_fields[m_selection_index]; + if (field.FieldDelegateHandleChar(key) == eKeyHandled) + return eKeyHandled; + + if (!field.FieldDelegateOnLastOrOnlyElement()) + return eKeyNotHandled; + + field.FieldDelegateExitCallback(); + + if (m_selection_index == GetNumberOfFields() - 1) { + m_selection_type = SelectionType::NewButton; + return eKeyHandled; + } + + m_selection_index++; + FieldDelegate &next_field = m_fields[m_selection_index]; + next_field.FieldDelegateSelectFirstElement(); + return eKeyHandled; + } + + HandleCharResult FieldDelegateHandleChar(int key) override { + switch (key) { + case '\r': + case '\n': + case KEY_ENTER: + switch (m_selection_type) { + case SelectionType::NewButton: + AddNewField(); + return eKeyHandled; + case SelectionType::RemoveButton: + RemoveField(); + return eKeyHandled; + case SelectionType::Field: + return SelectNextInList(key); + } + break; + case '\t': + return SelectNext(key); + case KEY_SHIFT_TAB: + return SelectPrevious(key); + default: + break; + } + + // If the key wasn't handled and one of the fields is selected, pass the key + // to that field. + if (m_selection_type == SelectionType::Field) { + return m_fields[m_selection_index].FieldDelegateHandleChar(key); + } + + return eKeyNotHandled; + } + + bool FieldDelegateOnLastOrOnlyElement() override { + if (m_selection_type == SelectionType::NewButton) { + return true; + } + return false; + } + + bool FieldDelegateOnFirstOrOnlyElement() override { + if (m_selection_type == SelectionType::NewButton && + GetNumberOfFields() == 0) + return true; + + if (m_selection_type == SelectionType::Field && m_selection_index == 0) { + FieldDelegate &field = m_fields[m_selection_index]; + return field.FieldDelegateOnFirstOrOnlyElement(); + } + + return false; + } + + void FieldDelegateSelectFirstElement() override { + if (GetNumberOfFields() == 0) { + m_selection_type = SelectionType::NewButton; + return; + } + + m_selection_type = SelectionType::Field; + m_selection_index = 0; + } + + void FieldDelegateSelectLastElement() override { + m_selection_type = SelectionType::NewButton; + } + + int GetNumberOfFields() { return m_fields.size(); } + + // Returns the form delegate at the current index. + T &GetField(int index) { return m_fields[index]; } + +protected: + std::string m_label; + // The default field delegate instance from which new field delegates will be + // created though a copy. + T m_default_field; + std::vector<T> m_fields; + int m_selection_index = 0; + // See SelectionType class enum. + SelectionType m_selection_type; +}; + +class ArgumentsFieldDelegate : public ListFieldDelegate<TextFieldDelegate> { +public: + ArgumentsFieldDelegate() + : ListFieldDelegate("Arguments", + TextFieldDelegate("Argument", "", false)) {} + + Args GetArguments() { + Args arguments; + for (int i = 0; i < GetNumberOfFields(); i++) { + arguments.AppendArgument(GetField(i).GetText()); + } + return arguments; + } + + void AddArguments(const Args &arguments) { + for (size_t i = 0; i < arguments.GetArgumentCount(); i++) { + AddNewField(); + TextFieldDelegate &field = GetField(GetNumberOfFields() - 1); + field.SetText(arguments.GetArgumentAtIndex(i)); + } + } +}; + +template <class KeyFieldDelegateType, class ValueFieldDelegateType> +class MappingFieldDelegate : public FieldDelegate { +public: + MappingFieldDelegate(KeyFieldDelegateType key_field, + ValueFieldDelegateType value_field) + : m_key_field(key_field), m_value_field(value_field), + m_selection_type(SelectionType::Key) {} + + // Signify which element is selected. The key field or its value field. + enum class SelectionType { Key, Value }; + + // A mapping field is drawn as two text fields with a right arrow in between. + // The first field stores the key of the mapping and the second stores the + // value if the mapping. + // + // __[Key]_____________ __[Value]___________ + // | | > | | + // |__________________| |__________________| + // - Error message if it exists. + + // The mapping field has a height that is equal to the maximum height between + // the key and value fields. + int FieldDelegateGetHeight() override { + return std::max(m_key_field.FieldDelegateGetHeight(), + m_value_field.FieldDelegateGetHeight()); + } + + void DrawArrow(Surface &surface) { + surface.MoveCursor(0, 1); + surface.PutChar(ACS_RARROW); + } + + void FieldDelegateDraw(Surface &surface, bool is_selected) override { + Rect bounds = surface.GetFrame(); + Rect key_field_bounds, arrow_and_value_field_bounds; + bounds.VerticalSplit(bounds.size.width / 2, key_field_bounds, + arrow_and_value_field_bounds); + Rect arrow_bounds, value_field_bounds; + arrow_and_value_field_bounds.VerticalSplit(1, arrow_bounds, + value_field_bounds); + + Surface key_field_surface = surface.SubSurface(key_field_bounds); + Surface arrow_surface = surface.SubSurface(arrow_bounds); + Surface value_field_surface = surface.SubSurface(value_field_bounds); + + bool key_is_selected = + m_selection_type == SelectionType::Key && is_selected; + m_key_field.FieldDelegateDraw(key_field_surface, key_is_selected); + DrawArrow(arrow_surface); + bool value_is_selected = + m_selection_type == SelectionType::Value && is_selected; + m_value_field.FieldDelegateDraw(value_field_surface, value_is_selected); + } + + HandleCharResult SelectNext(int key) { + if (FieldDelegateOnLastOrOnlyElement()) + return eKeyNotHandled; + + if (!m_key_field.FieldDelegateOnLastOrOnlyElement()) { + return m_key_field.FieldDelegateHandleChar(key); + } + + m_key_field.FieldDelegateExitCallback(); + m_selection_type = SelectionType::Value; + m_value_field.FieldDelegateSelectFirstElement(); + return eKeyHandled; + } + + HandleCharResult SelectPrevious(int key) { + if (FieldDelegateOnFirstOrOnlyElement()) + return eKeyNotHandled; + + if (!m_value_field.FieldDelegateOnFirstOrOnlyElement()) { + return m_value_field.FieldDelegateHandleChar(key); + } + + m_value_field.FieldDelegateExitCallback(); + m_selection_type = SelectionType::Key; + m_key_field.FieldDelegateSelectLastElement(); + return eKeyHandled; + } + + // If the value field is selected, pass the key to it. If the key field is + // selected, its last element is selected, and it didn't handle the key, then + // select its corresponding value field. + HandleCharResult SelectNextField(int key) { + if (m_selection_type == SelectionType::Value) { + return m_value_field.FieldDelegateHandleChar(key); + } + + if (m_key_field.FieldDelegateHandleChar(key) == eKeyHandled) + return eKeyHandled; + + if (!m_key_field.FieldDelegateOnLastOrOnlyElement()) + return eKeyNotHandled; + + m_key_field.FieldDelegateExitCallback(); + m_selection_type = SelectionType::Value; + m_value_field.FieldDelegateSelectFirstElement(); + return eKeyHandled; + } + + HandleCharResult FieldDelegateHandleChar(int key) override { + switch (key) { + case KEY_RETURN: + return SelectNextField(key); + case '\t': + return SelectNext(key); + case KEY_SHIFT_TAB: + return SelectPrevious(key); + default: + break; + } + + // If the key wasn't handled, pass the key to the selected field. + if (m_selection_type == SelectionType::Key) + return m_key_field.FieldDelegateHandleChar(key); + else + return m_value_field.FieldDelegateHandleChar(key); + + return eKeyNotHandled; + } + + bool FieldDelegateOnFirstOrOnlyElement() override { + return m_selection_type == SelectionType::Key; + } + + bool FieldDelegateOnLastOrOnlyElement() override { + return m_selection_type == SelectionType::Value; + } + + void FieldDelegateSelectFirstElement() override { + m_selection_type = SelectionType::Key; + } + + void FieldDelegateSelectLastElement() override { + m_selection_type = SelectionType::Value; + } + + bool FieldDelegateHasError() override { + return m_key_field.FieldDelegateHasError() || + m_value_field.FieldDelegateHasError(); + } + + KeyFieldDelegateType &GetKeyField() { return m_key_field; } + + ValueFieldDelegateType &GetValueField() { return m_value_field; } + +protected: + KeyFieldDelegateType m_key_field; + ValueFieldDelegateType m_value_field; + // See SelectionType class enum. + SelectionType m_selection_type; +}; + +class EnvironmentVariableNameFieldDelegate : public TextFieldDelegate { +public: + EnvironmentVariableNameFieldDelegate(const char *content) + : TextFieldDelegate("Name", content, true) {} + + // Environment variable names can't contain an equal sign. + bool IsAcceptableChar(int key) override { + return TextFieldDelegate::IsAcceptableChar(key) && key != '='; + } + + const std::string &GetName() { return m_content; } +}; + +class EnvironmentVariableFieldDelegate + : public MappingFieldDelegate<EnvironmentVariableNameFieldDelegate, + TextFieldDelegate> { +public: + EnvironmentVariableFieldDelegate() + : MappingFieldDelegate( + EnvironmentVariableNameFieldDelegate(""), + TextFieldDelegate("Value", "", /*required=*/false)) {} + + const std::string &GetName() { return GetKeyField().GetName(); } + + const std::string &GetValue() { return GetValueField().GetText(); } + + void SetName(const char *name) { return GetKeyField().SetText(name); } + + void SetValue(const char *value) { return GetValueField().SetText(value); } +}; + +class EnvironmentVariableListFieldDelegate + : public ListFieldDelegate<EnvironmentVariableFieldDelegate> { +public: + EnvironmentVariableListFieldDelegate(const char *label) + : ListFieldDelegate(label, EnvironmentVariableFieldDelegate()) {} + + Environment GetEnvironment() { + Environment environment; + for (int i = 0; i < GetNumberOfFields(); i++) { + environment.insert( + std::make_pair(GetField(i).GetName(), GetField(i).GetValue())); + } + return environment; + } + + void AddEnvironmentVariables(const Environment &environment) { + for (auto &variable : environment) { + AddNewField(); + EnvironmentVariableFieldDelegate &field = + GetField(GetNumberOfFields() - 1); + field.SetName(variable.getKey().str().c_str()); + field.SetValue(variable.getValue().c_str()); + } + } +}; + +class FormAction { +public: + FormAction(const char *label, std::function<void(Window &)> action) + : m_action(action) { + if (label) + m_label = label; + } + + // Draw a centered [Label]. + void Draw(Surface &surface, bool is_selected) { + int x = (surface.GetWidth() - m_label.length()) / 2; + surface.MoveCursor(x, 0); + if (is_selected) + surface.AttributeOn(A_REVERSE); + surface.PutChar('['); + surface.PutCString(m_label.c_str()); + surface.PutChar(']'); + if (is_selected) + surface.AttributeOff(A_REVERSE); + } + + void Execute(Window &window) { m_action(window); } + + const std::string &GetLabel() { return m_label; } + +protected: + std::string m_label; + std::function<void(Window &)> m_action; +}; + +class FormDelegate { +public: + FormDelegate() = default; + + virtual ~FormDelegate() = default; + + virtual std::string GetName() = 0; + + virtual void UpdateFieldsVisibility() {} + + FieldDelegate *GetField(uint32_t field_index) { + if (field_index < m_fields.size()) + return m_fields[field_index].get(); + return nullptr; + } + + FormAction &GetAction(int action_index) { return m_actions[action_index]; } + + int GetNumberOfFields() { return m_fields.size(); } + + int GetNumberOfActions() { return m_actions.size(); } + + bool HasError() { return !m_error.empty(); } + + void ClearError() { m_error.clear(); } + + const std::string &GetError() { return m_error; } + + void SetError(const char *error) { m_error = error; } + + // If all fields are valid, true is returned. Otherwise, an error message is + // set and false is returned. This method is usually called at the start of an + // action that requires valid fields. + bool CheckFieldsValidity() { + for (int i = 0; i < GetNumberOfFields(); i++) { + GetField(i)->FieldDelegateExitCallback(); + if (GetField(i)->FieldDelegateHasError()) { + SetError("Some fields are invalid!"); + return false; + } + } + return true; + } + + // Factory methods to create and add fields of specific types. + + TextFieldDelegate *AddTextField(const char *label, const char *content, + bool required) { + TextFieldDelegate *delegate = + new TextFieldDelegate(label, content, required); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + FileFieldDelegate *AddFileField(const char *label, const char *content, + bool need_to_exist, bool required) { + FileFieldDelegate *delegate = + new FileFieldDelegate(label, content, need_to_exist, required); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + DirectoryFieldDelegate *AddDirectoryField(const char *label, + const char *content, + bool need_to_exist, bool required) { + DirectoryFieldDelegate *delegate = + new DirectoryFieldDelegate(label, content, need_to_exist, required); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + ArchFieldDelegate *AddArchField(const char *label, const char *content, + bool required) { + ArchFieldDelegate *delegate = + new ArchFieldDelegate(label, content, required); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + IntegerFieldDelegate *AddIntegerField(const char *label, int content, + bool required) { + IntegerFieldDelegate *delegate = + new IntegerFieldDelegate(label, content, required); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + BooleanFieldDelegate *AddBooleanField(const char *label, bool content) { + BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + LazyBooleanFieldDelegate *AddLazyBooleanField(const char *label, + const char *calculate_label) { + LazyBooleanFieldDelegate *delegate = + new LazyBooleanFieldDelegate(label, calculate_label); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + ChoicesFieldDelegate *AddChoicesField(const char *label, int height, + std::vector<std::string> choices) { + ChoicesFieldDelegate *delegate = + new ChoicesFieldDelegate(label, height, choices); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + PlatformPluginFieldDelegate *AddPlatformPluginField(Debugger &debugger) { + PlatformPluginFieldDelegate *delegate = + new PlatformPluginFieldDelegate(debugger); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + ProcessPluginFieldDelegate *AddProcessPluginField() { + ProcessPluginFieldDelegate *delegate = new ProcessPluginFieldDelegate(); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + template <class T> + ListFieldDelegate<T> *AddListField(const char *label, T default_field) { + ListFieldDelegate<T> *delegate = + new ListFieldDelegate<T>(label, default_field); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + ArgumentsFieldDelegate *AddArgumentsField() { + ArgumentsFieldDelegate *delegate = new ArgumentsFieldDelegate(); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + template <class K, class V> + MappingFieldDelegate<K, V> *AddMappingField(K key_field, V value_field) { + MappingFieldDelegate<K, V> *delegate = + new MappingFieldDelegate<K, V>(key_field, value_field); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + EnvironmentVariableNameFieldDelegate * + AddEnvironmentVariableNameField(const char *content) { + EnvironmentVariableNameFieldDelegate *delegate = + new EnvironmentVariableNameFieldDelegate(content); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + EnvironmentVariableFieldDelegate *AddEnvironmentVariableField() { + EnvironmentVariableFieldDelegate *delegate = + new EnvironmentVariableFieldDelegate(); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + EnvironmentVariableListFieldDelegate * + AddEnvironmentVariableListField(const char *label) { + EnvironmentVariableListFieldDelegate *delegate = + new EnvironmentVariableListFieldDelegate(label); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + // Factory methods for adding actions. + + void AddAction(const char *label, std::function<void(Window &)> action) { + m_actions.push_back(FormAction(label, action)); + } + +protected: + std::vector<FieldDelegateUP> m_fields; + std::vector<FormAction> m_actions; + // Optional error message. If empty, form is considered to have no error. + std::string m_error; +}; + +typedef std::shared_ptr<FormDelegate> FormDelegateSP; + +class FormWindowDelegate : public WindowDelegate { +public: + FormWindowDelegate(FormDelegateSP &delegate_sp) : m_delegate_sp(delegate_sp) { + assert(m_delegate_sp->GetNumberOfActions() > 0); + if (m_delegate_sp->GetNumberOfFields() > 0) + m_selection_type = SelectionType::Field; + else + m_selection_type = SelectionType::Action; + } + + // Signify which element is selected. If a field or an action is selected, + // then m_selection_index signifies the particular field or action that is + // selected. + enum class SelectionType { Field, Action }; + + // A form window is padded by one character from all sides. First, if an error + // message exists, it is drawn followed by a separator. Then one or more + // fields are drawn. Finally, all available actions are drawn on a single + // line. + // + // ___<Form Name>_________________________________________________ + // | | + // | - Error message if it exists. | + // |-------------------------------------------------------------| + // | Form elements here. | + // | Form actions here. | + // | | + // |______________________________________[Press Esc to cancel]__| + // + + // One line for the error and another for the horizontal line. + int GetErrorHeight() { + if (m_delegate_sp->HasError()) + return 2; + return 0; + } + + // Actions span a single line. + int GetActionsHeight() { + if (m_delegate_sp->GetNumberOfActions() > 0) + return 1; + return 0; + } + + // Get the total number of needed lines to draw the contents. + int GetContentHeight() { + int height = 0; + height += GetErrorHeight(); + for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) { + if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible()) + continue; + height += m_delegate_sp->GetField(i)->FieldDelegateGetHeight(); + } + height += GetActionsHeight(); + return height; + } + + ScrollContext GetScrollContext() { + if (m_selection_type == SelectionType::Action) + return ScrollContext(GetContentHeight() - 1); + + FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); + ScrollContext context = field->FieldDelegateGetScrollContext(); + + int offset = GetErrorHeight(); + for (int i = 0; i < m_selection_index; i++) { + if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible()) + continue; + offset += m_delegate_sp->GetField(i)->FieldDelegateGetHeight(); + } + context.Offset(offset); + + // If the context is touching the error, include the error in the context as + // well. + if (context.start == GetErrorHeight()) + context.start = 0; + + return context; + } + + void UpdateScrolling(Surface &surface) { + ScrollContext context = GetScrollContext(); + int content_height = GetContentHeight(); + int surface_height = surface.GetHeight(); + int visible_height = std::min(content_height, surface_height); + int last_visible_line = m_first_visible_line + visible_height - 1; + + // If the last visible line is bigger than the content, then it is invalid + // and needs to be set to the last line in the content. This can happen when + // a field has shrunk in height. + if (last_visible_line > content_height - 1) { + m_first_visible_line = content_height - visible_height; + } + + if (context.start < m_first_visible_line) { + m_first_visible_line = context.start; + return; + } + + if (context.end > last_visible_line) { + m_first_visible_line = context.end - visible_height + 1; + } + } + + void DrawError(Surface &surface) { + if (!m_delegate_sp->HasError()) + return; + surface.MoveCursor(0, 0); + surface.AttributeOn(COLOR_PAIR(RedOnBlack)); + surface.PutChar(ACS_DIAMOND); + surface.PutChar(' '); + surface.PutCStringTruncated(1, m_delegate_sp->GetError().c_str()); + surface.AttributeOff(COLOR_PAIR(RedOnBlack)); + + surface.MoveCursor(0, 1); + surface.HorizontalLine(surface.GetWidth()); + } + + void DrawFields(Surface &surface) { + int line = 0; + int width = surface.GetWidth(); + bool a_field_is_selected = m_selection_type == SelectionType::Field; + for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) { + FieldDelegate *field = m_delegate_sp->GetField(i); + if (!field->FieldDelegateIsVisible()) + continue; + bool is_field_selected = a_field_is_selected && m_selection_index == i; + int height = field->FieldDelegateGetHeight(); + Rect bounds = Rect(Point(0, line), Size(width, height)); + Surface field_surface = surface.SubSurface(bounds); + field->FieldDelegateDraw(field_surface, is_field_selected); + line += height; + } + } + + void DrawActions(Surface &surface) { + int number_of_actions = m_delegate_sp->GetNumberOfActions(); + int width = surface.GetWidth() / number_of_actions; + bool an_action_is_selected = m_selection_type == SelectionType::Action; + int x = 0; + for (int i = 0; i < number_of_actions; i++) { + bool is_action_selected = an_action_is_selected && m_selection_index == i; + FormAction &action = m_delegate_sp->GetAction(i); + Rect bounds = Rect(Point(x, 0), Size(width, 1)); + Surface action_surface = surface.SubSurface(bounds); + action.Draw(action_surface, is_action_selected); + x += width; + } + } + + void DrawElements(Surface &surface) { + Rect frame = surface.GetFrame(); + Rect fields_bounds, actions_bounds; + frame.HorizontalSplit(surface.GetHeight() - GetActionsHeight(), + fields_bounds, actions_bounds); + Surface fields_surface = surface.SubSurface(fields_bounds); + Surface actions_surface = surface.SubSurface(actions_bounds); + + DrawFields(fields_surface); + DrawActions(actions_surface); + } + + // Contents are first drawn on a pad. Then a subset of that pad is copied to + // the derived window starting at the first visible line. This essentially + // provides scrolling functionality. + void DrawContent(Surface &surface) { + UpdateScrolling(surface); + + int width = surface.GetWidth(); + int height = GetContentHeight(); + Pad pad = Pad(Size(width, height)); + + Rect frame = pad.GetFrame(); + Rect error_bounds, elements_bounds; + frame.HorizontalSplit(GetErrorHeight(), error_bounds, elements_bounds); + Surface error_surface = pad.SubSurface(error_bounds); + Surface elements_surface = pad.SubSurface(elements_bounds); + + DrawError(error_surface); + DrawElements(elements_surface); + + int copy_height = std::min(surface.GetHeight(), pad.GetHeight()); + pad.CopyToSurface(surface, Point(0, m_first_visible_line), Point(), + Size(width, copy_height)); + } + + void DrawSubmitHint(Surface &surface, bool is_active) { + surface.MoveCursor(2, surface.GetHeight() - 1); + if (is_active) + surface.AttributeOn(A_BOLD | COLOR_PAIR(BlackOnWhite)); + surface.Printf("[Press Alt+Enter to %s]", + m_delegate_sp->GetAction(0).GetLabel().c_str()); + if (is_active) + surface.AttributeOff(A_BOLD | COLOR_PAIR(BlackOnWhite)); + } + + bool WindowDelegateDraw(Window &window, bool force) override { + m_delegate_sp->UpdateFieldsVisibility(); + + window.Erase(); + + window.DrawTitleBox(m_delegate_sp->GetName().c_str(), + "Press Esc to Cancel"); + DrawSubmitHint(window, window.IsActive()); + + Rect content_bounds = window.GetFrame(); + content_bounds.Inset(2, 2); + Surface content_surface = window.SubSurface(content_bounds); + + DrawContent(content_surface); + return true; + } + + void SkipNextHiddenFields() { + while (true) { + if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible()) + return; + + if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) { + m_selection_type = SelectionType::Action; + m_selection_index = 0; + return; + } + + m_selection_index++; + } + } + + HandleCharResult SelectNext(int key) { + if (m_selection_type == SelectionType::Action) { + if (m_selection_index < m_delegate_sp->GetNumberOfActions() - 1) { + m_selection_index++; + return eKeyHandled; + } + + m_selection_index = 0; + m_selection_type = SelectionType::Field; + SkipNextHiddenFields(); + if (m_selection_type == SelectionType::Field) { + FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index); + next_field->FieldDelegateSelectFirstElement(); + } + return eKeyHandled; + } + + FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); + if (!field->FieldDelegateOnLastOrOnlyElement()) { + return field->FieldDelegateHandleChar(key); + } + + field->FieldDelegateExitCallback(); + + if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) { + m_selection_type = SelectionType::Action; + m_selection_index = 0; + return eKeyHandled; + } + + m_selection_index++; + SkipNextHiddenFields(); + + if (m_selection_type == SelectionType::Field) { + FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index); + next_field->FieldDelegateSelectFirstElement(); + } + + return eKeyHandled; + } + + void SkipPreviousHiddenFields() { + while (true) { + if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible()) + return; + + if (m_selection_index == 0) { + m_selection_type = SelectionType::Action; + m_selection_index = 0; + return; + } + + m_selection_index--; + } + } + + HandleCharResult SelectPrevious(int key) { + if (m_selection_type == SelectionType::Action) { + if (m_selection_index > 0) { + m_selection_index--; + return eKeyHandled; + } + m_selection_index = m_delegate_sp->GetNumberOfFields() - 1; + m_selection_type = SelectionType::Field; + SkipPreviousHiddenFields(); + if (m_selection_type == SelectionType::Field) { + FieldDelegate *previous_field = + m_delegate_sp->GetField(m_selection_index); + previous_field->FieldDelegateSelectLastElement(); + } + return eKeyHandled; + } + + FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); + if (!field->FieldDelegateOnFirstOrOnlyElement()) { + return field->FieldDelegateHandleChar(key); + } + + field->FieldDelegateExitCallback(); + + if (m_selection_index == 0) { + m_selection_type = SelectionType::Action; + m_selection_index = m_delegate_sp->GetNumberOfActions() - 1; + return eKeyHandled; + } + + m_selection_index--; + SkipPreviousHiddenFields(); + + if (m_selection_type == SelectionType::Field) { + FieldDelegate *previous_field = + m_delegate_sp->GetField(m_selection_index); + previous_field->FieldDelegateSelectLastElement(); + } + + return eKeyHandled; + } + + void ExecuteAction(Window &window, int index) { + FormAction &action = m_delegate_sp->GetAction(index); + action.Execute(window); + if (m_delegate_sp->HasError()) { + m_first_visible_line = 0; + m_selection_index = 0; + m_selection_type = SelectionType::Field; + } + } + + // Always return eKeyHandled to absorb all events since forms are always + // added as pop-ups that should take full control until canceled or submitted. + HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { + switch (key) { + case '\r': + case '\n': + case KEY_ENTER: + if (m_selection_type == SelectionType::Action) { + ExecuteAction(window, m_selection_index); + return eKeyHandled; + } + break; + case KEY_ALT_ENTER: + ExecuteAction(window, 0); + return eKeyHandled; + case '\t': + SelectNext(key); + return eKeyHandled; + case KEY_SHIFT_TAB: + SelectPrevious(key); + return eKeyHandled; + case KEY_ESCAPE: + window.GetParent()->RemoveSubWindow(&window); + return eKeyHandled; + default: + break; + } + + // If the key wasn't handled and one of the fields is selected, pass the key + // to that field. + if (m_selection_type == SelectionType::Field) { + FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); + if (field->FieldDelegateHandleChar(key) == eKeyHandled) + return eKeyHandled; + } + + // If the key wasn't handled by the possibly selected field, handle some + // extra keys for navigation. + switch (key) { + case KEY_DOWN: + SelectNext(key); + return eKeyHandled; + case KEY_UP: + SelectPrevious(key); + return eKeyHandled; + default: + break; + } + + return eKeyHandled; + } + +protected: + FormDelegateSP m_delegate_sp; + // The index of the currently selected SelectionType. + int m_selection_index = 0; + // See SelectionType class enum. + SelectionType m_selection_type; + // The first visible line from the pad. + int m_first_visible_line = 0; +}; + +/////////////////////////// +// Form Delegate Instances +/////////////////////////// + +class DetachOrKillProcessFormDelegate : public FormDelegate { +public: + DetachOrKillProcessFormDelegate(Process *process) : m_process(process) { + SetError("There is a running process, either detach or kill it."); + + m_keep_stopped_field = + AddBooleanField("Keep process stopped when detaching.", false); + + AddAction("Detach", [this](Window &window) { Detach(window); }); + AddAction("Kill", [this](Window &window) { Kill(window); }); + } + + std::string GetName() override { return "Detach/Kill Process"; } + + void Kill(Window &window) { + Status destroy_status(m_process->Destroy(false)); + if (destroy_status.Fail()) { + SetError("Failed to kill process."); + return; + } + window.GetParent()->RemoveSubWindow(&window); + } + + void Detach(Window &window) { + Status detach_status(m_process->Detach(m_keep_stopped_field->GetBoolean())); + if (detach_status.Fail()) { + SetError("Failed to detach from process."); + return; + } + window.GetParent()->RemoveSubWindow(&window); + } + +protected: + Process *m_process; + BooleanFieldDelegate *m_keep_stopped_field; +}; + +class ProcessAttachFormDelegate : public FormDelegate { +public: + ProcessAttachFormDelegate(Debugger &debugger, WindowSP main_window_sp) + : m_debugger(debugger), m_main_window_sp(main_window_sp) { + std::vector<std::string> types; + types.push_back(std::string("Name")); + types.push_back(std::string("PID")); + m_type_field = AddChoicesField("Attach By", 2, types); + m_pid_field = AddIntegerField("PID", 0, true); + m_name_field = + AddTextField("Process Name", GetDefaultProcessName().c_str(), true); + m_continue_field = AddBooleanField("Continue once attached.", false); + m_wait_for_field = AddBooleanField("Wait for process to launch.", false); + m_include_existing_field = + AddBooleanField("Include existing processes.", false); + m_show_advanced_field = AddBooleanField("Show advanced settings.", false); + m_plugin_field = AddProcessPluginField(); + + AddAction("Attach", [this](Window &window) { Attach(window); }); + } + + std::string GetName() override { return "Attach Process"; } + + void UpdateFieldsVisibility() override { + if (m_type_field->GetChoiceContent() == "Name") { + m_pid_field->FieldDelegateHide(); + m_name_field->FieldDelegateShow(); + m_wait_for_field->FieldDelegateShow(); + if (m_wait_for_field->GetBoolean()) + m_include_existing_field->FieldDelegateShow(); + else + m_include_existing_field->FieldDelegateHide(); + } else { + m_pid_field->FieldDelegateShow(); + m_name_field->FieldDelegateHide(); + m_wait_for_field->FieldDelegateHide(); + m_include_existing_field->FieldDelegateHide(); + } + if (m_show_advanced_field->GetBoolean()) + m_plugin_field->FieldDelegateShow(); + else + m_plugin_field->FieldDelegateHide(); + } + + // Get the basename of the target's main executable if available, empty string + // otherwise. + std::string GetDefaultProcessName() { + Target *target = m_debugger.GetSelectedTarget().get(); + if (target == nullptr) + return ""; + + ModuleSP module_sp = target->GetExecutableModule(); + if (!module_sp->IsExecutable()) + return ""; + + return module_sp->GetFileSpec().GetFilename().AsCString(); + } + + bool StopRunningProcess() { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + + if (!exe_ctx.HasProcessScope()) + return false; + + Process *process = exe_ctx.GetProcessPtr(); + if (!(process && process->IsAlive())) + return false; + + FormDelegateSP form_delegate_sp = + FormDelegateSP(new DetachOrKillProcessFormDelegate(process)); + Rect bounds = m_main_window_sp->GetCenteredRect(85, 8); + WindowSP form_window_sp = m_main_window_sp->CreateSubWindow( + form_delegate_sp->GetName().c_str(), bounds, true); + WindowDelegateSP window_delegate_sp = + WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); + form_window_sp->SetDelegate(window_delegate_sp); + + return true; + } + + Target *GetTarget() { + Target *target = m_debugger.GetSelectedTarget().get(); + + if (target != nullptr) + return target; + + TargetSP new_target_sp; + m_debugger.GetTargetList().CreateTarget( + m_debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp); + + target = new_target_sp.get(); + + if (target == nullptr) + SetError("Failed to create target."); + + m_debugger.GetTargetList().SetSelectedTarget(new_target_sp); + + return target; + } + + ProcessAttachInfo GetAttachInfo() { + ProcessAttachInfo attach_info; + attach_info.SetContinueOnceAttached(m_continue_field->GetBoolean()); + if (m_type_field->GetChoiceContent() == "Name") { + attach_info.GetExecutableFile().SetFile(m_name_field->GetText(), + FileSpec::Style::native); + attach_info.SetWaitForLaunch(m_wait_for_field->GetBoolean()); + if (m_wait_for_field->GetBoolean()) + attach_info.SetIgnoreExisting(!m_include_existing_field->GetBoolean()); + } else { + attach_info.SetProcessID(m_pid_field->GetInteger()); + } + attach_info.SetProcessPluginName(m_plugin_field->GetPluginName()); + + return attach_info; + } + + void Attach(Window &window) { + ClearError(); + + bool all_fields_are_valid = CheckFieldsValidity(); + if (!all_fields_are_valid) + return; + + bool process_is_running = StopRunningProcess(); + if (process_is_running) + return; + + Target *target = GetTarget(); + if (HasError()) + return; + + StreamString stream; + ProcessAttachInfo attach_info = GetAttachInfo(); + Status status = target->Attach(attach_info, &stream); + + if (status.Fail()) { + SetError(status.AsCString()); + return; + } + + ProcessSP process_sp(target->GetProcessSP()); + if (!process_sp) { + SetError("Attached sucessfully but target has no process."); + return; + } + + if (attach_info.GetContinueOnceAttached()) + process_sp->Resume(); + + window.GetParent()->RemoveSubWindow(&window); + } + +protected: + Debugger &m_debugger; + WindowSP m_main_window_sp; + + ChoicesFieldDelegate *m_type_field; + IntegerFieldDelegate *m_pid_field; + TextFieldDelegate *m_name_field; + BooleanFieldDelegate *m_continue_field; + BooleanFieldDelegate *m_wait_for_field; + BooleanFieldDelegate *m_include_existing_field; + BooleanFieldDelegate *m_show_advanced_field; + ProcessPluginFieldDelegate *m_plugin_field; +}; + +class TargetCreateFormDelegate : public FormDelegate { +public: + TargetCreateFormDelegate(Debugger &debugger) : m_debugger(debugger) { + m_executable_field = AddFileField("Executable", "", /*need_to_exist=*/true, + /*required=*/true); + m_core_file_field = AddFileField("Core File", "", /*need_to_exist=*/true, + /*required=*/false); + m_symbol_file_field = AddFileField( + "Symbol File", "", /*need_to_exist=*/true, /*required=*/false); + m_show_advanced_field = AddBooleanField("Show advanced settings.", false); + m_remote_file_field = AddFileField( + "Remote File", "", /*need_to_exist=*/false, /*required=*/false); + m_arch_field = AddArchField("Architecture", "", /*required=*/false); + m_platform_field = AddPlatformPluginField(debugger); + m_load_dependent_files_field = + AddChoicesField("Load Dependents", 3, GetLoadDependentFilesChoices()); + + AddAction("Create", [this](Window &window) { CreateTarget(window); }); + } + + std::string GetName() override { return "Create Target"; } + + void UpdateFieldsVisibility() override { + if (m_show_advanced_field->GetBoolean()) { + m_remote_file_field->FieldDelegateShow(); + m_arch_field->FieldDelegateShow(); + m_platform_field->FieldDelegateShow(); + m_load_dependent_files_field->FieldDelegateShow(); + } else { + m_remote_file_field->FieldDelegateHide(); + m_arch_field->FieldDelegateHide(); + m_platform_field->FieldDelegateHide(); + m_load_dependent_files_field->FieldDelegateHide(); + } + } + + static constexpr const char *kLoadDependentFilesNo = "No"; + static constexpr const char *kLoadDependentFilesYes = "Yes"; + static constexpr const char *kLoadDependentFilesExecOnly = "Executable only"; + + std::vector<std::string> GetLoadDependentFilesChoices() { + std::vector<std::string> load_dependents_options; + load_dependents_options.push_back(kLoadDependentFilesExecOnly); + load_dependents_options.push_back(kLoadDependentFilesYes); + load_dependents_options.push_back(kLoadDependentFilesNo); + return load_dependents_options; + } + + LoadDependentFiles GetLoadDependentFiles() { + std::string choice = m_load_dependent_files_field->GetChoiceContent(); + if (choice == kLoadDependentFilesNo) + return eLoadDependentsNo; + if (choice == kLoadDependentFilesYes) + return eLoadDependentsYes; + return eLoadDependentsDefault; + } + + OptionGroupPlatform GetPlatformOptions() { + OptionGroupPlatform platform_options(false); + platform_options.SetPlatformName(m_platform_field->GetPluginName().c_str()); + return platform_options; + } + + TargetSP GetTarget() { + OptionGroupPlatform platform_options = GetPlatformOptions(); + TargetSP target_sp; + Status status = m_debugger.GetTargetList().CreateTarget( + m_debugger, m_executable_field->GetPath(), + m_arch_field->GetArchString(), GetLoadDependentFiles(), + &platform_options, target_sp); + + if (status.Fail()) { + SetError(status.AsCString()); + return nullptr; + } + + m_debugger.GetTargetList().SetSelectedTarget(target_sp); + + return target_sp; + } + + void SetSymbolFile(TargetSP target_sp) { + if (!m_symbol_file_field->IsSpecified()) + return; + + ModuleSP module_sp(target_sp->GetExecutableModule()); + if (!module_sp) + return; + + module_sp->SetSymbolFileFileSpec( + m_symbol_file_field->GetResolvedFileSpec()); + } + + void SetCoreFile(TargetSP target_sp) { + if (!m_core_file_field->IsSpecified()) + return; + + FileSpec core_file_spec = m_core_file_field->GetResolvedFileSpec(); + + FileSpec core_file_directory_spec; + core_file_directory_spec.SetDirectory(core_file_spec.GetDirectory()); + target_sp->AppendExecutableSearchPaths(core_file_directory_spec); + + ProcessSP process_sp(target_sp->CreateProcess( + m_debugger.GetListener(), llvm::StringRef(), &core_file_spec, false)); + + if (!process_sp) { + SetError("Unknown core file format!"); + return; + } + + Status status = process_sp->LoadCore(); + if (status.Fail()) { + SetError("Unknown core file format!"); + return; + } + } + + void SetRemoteFile(TargetSP target_sp) { + if (!m_remote_file_field->IsSpecified()) + return; + + ModuleSP module_sp(target_sp->GetExecutableModule()); + if (!module_sp) + return; + + FileSpec remote_file_spec = m_remote_file_field->GetFileSpec(); + module_sp->SetPlatformFileSpec(remote_file_spec); + } + + void RemoveTarget(TargetSP target_sp) { + m_debugger.GetTargetList().DeleteTarget(target_sp); + } + + void CreateTarget(Window &window) { + ClearError(); + + bool all_fields_are_valid = CheckFieldsValidity(); + if (!all_fields_are_valid) + return; + + TargetSP target_sp = GetTarget(); + if (HasError()) + return; + + SetSymbolFile(target_sp); + if (HasError()) { + RemoveTarget(target_sp); + return; + } + + SetCoreFile(target_sp); + if (HasError()) { + RemoveTarget(target_sp); + return; + } + + SetRemoteFile(target_sp); + if (HasError()) { + RemoveTarget(target_sp); + return; + } + + window.GetParent()->RemoveSubWindow(&window); + } + +protected: + Debugger &m_debugger; + + FileFieldDelegate *m_executable_field; + FileFieldDelegate *m_core_file_field; + FileFieldDelegate *m_symbol_file_field; + BooleanFieldDelegate *m_show_advanced_field; + FileFieldDelegate *m_remote_file_field; + ArchFieldDelegate *m_arch_field; + PlatformPluginFieldDelegate *m_platform_field; + ChoicesFieldDelegate *m_load_dependent_files_field; +}; + +class ProcessLaunchFormDelegate : public FormDelegate { +public: + ProcessLaunchFormDelegate(Debugger &debugger, WindowSP main_window_sp) + : m_debugger(debugger), m_main_window_sp(main_window_sp) { + + m_arguments_field = AddArgumentsField(); + SetArgumentsFieldDefaultValue(); + m_target_environment_field = + AddEnvironmentVariableListField("Target Environment Variables"); + SetTargetEnvironmentFieldDefaultValue(); + m_working_directory_field = AddDirectoryField( + "Working Directory", GetDefaultWorkingDirectory().c_str(), true, false); + + m_show_advanced_field = AddBooleanField("Show advanced settings.", false); + + m_stop_at_entry_field = AddBooleanField("Stop at entry point.", false); + m_detach_on_error_field = + AddBooleanField("Detach on error.", GetDefaultDetachOnError()); + m_disable_aslr_field = + AddBooleanField("Disable ASLR", GetDefaultDisableASLR()); + m_plugin_field = AddProcessPluginField(); + m_arch_field = AddArchField("Architecture", "", false); + m_shell_field = AddFileField("Shell", "", true, false); + m_expand_shell_arguments_field = + AddBooleanField("Expand shell arguments.", false); + + m_disable_standard_io_field = + AddBooleanField("Disable Standard IO", GetDefaultDisableStandardIO()); + m_standard_output_field = + AddFileField("Standard Output File", "", /*need_to_exist=*/false, + /*required=*/false); + m_standard_error_field = + AddFileField("Standard Error File", "", /*need_to_exist=*/false, + /*required=*/false); + m_standard_input_field = + AddFileField("Standard Input File", "", /*need_to_exist=*/false, + /*required=*/false); + + m_show_inherited_environment_field = + AddBooleanField("Show inherited environment variables.", false); + m_inherited_environment_field = + AddEnvironmentVariableListField("Inherited Environment Variables"); + SetInheritedEnvironmentFieldDefaultValue(); + + AddAction("Launch", [this](Window &window) { Launch(window); }); + } + + std::string GetName() override { return "Launch Process"; } + + void UpdateFieldsVisibility() override { + if (m_show_advanced_field->GetBoolean()) { + m_stop_at_entry_field->FieldDelegateShow(); + m_detach_on_error_field->FieldDelegateShow(); + m_disable_aslr_field->FieldDelegateShow(); + m_plugin_field->FieldDelegateShow(); + m_arch_field->FieldDelegateShow(); + m_shell_field->FieldDelegateShow(); + m_expand_shell_arguments_field->FieldDelegateShow(); + m_disable_standard_io_field->FieldDelegateShow(); + if (m_disable_standard_io_field->GetBoolean()) { + m_standard_input_field->FieldDelegateHide(); + m_standard_output_field->FieldDelegateHide(); + m_standard_error_field->FieldDelegateHide(); + } else { + m_standard_input_field->FieldDelegateShow(); + m_standard_output_field->FieldDelegateShow(); + m_standard_error_field->FieldDelegateShow(); + } + m_show_inherited_environment_field->FieldDelegateShow(); + if (m_show_inherited_environment_field->GetBoolean()) + m_inherited_environment_field->FieldDelegateShow(); + else + m_inherited_environment_field->FieldDelegateHide(); + } else { + m_stop_at_entry_field->FieldDelegateHide(); + m_detach_on_error_field->FieldDelegateHide(); + m_disable_aslr_field->FieldDelegateHide(); + m_plugin_field->FieldDelegateHide(); + m_arch_field->FieldDelegateHide(); + m_shell_field->FieldDelegateHide(); + m_expand_shell_arguments_field->FieldDelegateHide(); + m_disable_standard_io_field->FieldDelegateHide(); + m_standard_input_field->FieldDelegateHide(); + m_standard_output_field->FieldDelegateHide(); + m_standard_error_field->FieldDelegateHide(); + m_show_inherited_environment_field->FieldDelegateHide(); + m_inherited_environment_field->FieldDelegateHide(); + } + } + + // Methods for setting the default value of the fields. + + void SetArgumentsFieldDefaultValue() { + TargetSP target = m_debugger.GetSelectedTarget(); + if (target == nullptr) + return; + + const Args &target_arguments = + target->GetProcessLaunchInfo().GetArguments(); + m_arguments_field->AddArguments(target_arguments); + } + + void SetTargetEnvironmentFieldDefaultValue() { + TargetSP target = m_debugger.GetSelectedTarget(); + if (target == nullptr) + return; + + const Environment &target_environment = target->GetTargetEnvironment(); + m_target_environment_field->AddEnvironmentVariables(target_environment); + } + + void SetInheritedEnvironmentFieldDefaultValue() { + TargetSP target = m_debugger.GetSelectedTarget(); + if (target == nullptr) + return; + + const Environment &inherited_environment = + target->GetInheritedEnvironment(); + m_inherited_environment_field->AddEnvironmentVariables( + inherited_environment); + } + + std::string GetDefaultWorkingDirectory() { + TargetSP target = m_debugger.GetSelectedTarget(); + if (target == nullptr) + return ""; + + PlatformSP platform = target->GetPlatform(); + return platform->GetWorkingDirectory().GetPath(); + } + + bool GetDefaultDisableASLR() { + TargetSP target = m_debugger.GetSelectedTarget(); + if (target == nullptr) + return false; + + return target->GetDisableASLR(); + } + + bool GetDefaultDisableStandardIO() { + TargetSP target = m_debugger.GetSelectedTarget(); + if (target == nullptr) + return true; + + return target->GetDisableSTDIO(); + } + + bool GetDefaultDetachOnError() { + TargetSP target = m_debugger.GetSelectedTarget(); + if (target == nullptr) + return true; + + return target->GetDetachOnError(); + } + + // Methods for getting the necessary information and setting them to the + // ProcessLaunchInfo. + + void GetExecutableSettings(ProcessLaunchInfo &launch_info) { + TargetSP target = m_debugger.GetSelectedTarget(); + ModuleSP executable_module = target->GetExecutableModule(); + llvm::StringRef target_settings_argv0 = target->GetArg0(); + + if (!target_settings_argv0.empty()) { + launch_info.GetArguments().AppendArgument(target_settings_argv0); + launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(), + false); + return; + } + + launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(), + true); + } + + void GetArguments(ProcessLaunchInfo &launch_info) { + TargetSP target = m_debugger.GetSelectedTarget(); + Args arguments = m_arguments_field->GetArguments(); + launch_info.GetArguments().AppendArguments(arguments); + } + + void GetEnvironment(ProcessLaunchInfo &launch_info) { + Environment target_environment = + m_target_environment_field->GetEnvironment(); + Environment inherited_environment = + m_inherited_environment_field->GetEnvironment(); + launch_info.GetEnvironment().insert(target_environment.begin(), + target_environment.end()); + launch_info.GetEnvironment().insert(inherited_environment.begin(), + inherited_environment.end()); + } + + void GetWorkingDirectory(ProcessLaunchInfo &launch_info) { + if (m_working_directory_field->IsSpecified()) + launch_info.SetWorkingDirectory( + m_working_directory_field->GetResolvedFileSpec()); + } + + void GetStopAtEntry(ProcessLaunchInfo &launch_info) { + if (m_stop_at_entry_field->GetBoolean()) + launch_info.GetFlags().Set(eLaunchFlagStopAtEntry); + else + launch_info.GetFlags().Clear(eLaunchFlagStopAtEntry); + } + + void GetDetachOnError(ProcessLaunchInfo &launch_info) { + if (m_detach_on_error_field->GetBoolean()) + launch_info.GetFlags().Set(eLaunchFlagDetachOnError); + else + launch_info.GetFlags().Clear(eLaunchFlagDetachOnError); + } + + void GetDisableASLR(ProcessLaunchInfo &launch_info) { + if (m_disable_aslr_field->GetBoolean()) + launch_info.GetFlags().Set(eLaunchFlagDisableASLR); + else + launch_info.GetFlags().Clear(eLaunchFlagDisableASLR); + } + + void GetPlugin(ProcessLaunchInfo &launch_info) { + launch_info.SetProcessPluginName(m_plugin_field->GetPluginName()); + } + + void GetArch(ProcessLaunchInfo &launch_info) { + if (!m_arch_field->IsSpecified()) + return; + + TargetSP target_sp = m_debugger.GetSelectedTarget(); + PlatformSP platform_sp = + target_sp ? target_sp->GetPlatform() : PlatformSP(); + launch_info.GetArchitecture() = Platform::GetAugmentedArchSpec( + platform_sp.get(), m_arch_field->GetArchString()); + } + + void GetShell(ProcessLaunchInfo &launch_info) { + if (!m_shell_field->IsSpecified()) + return; + + launch_info.SetShell(m_shell_field->GetResolvedFileSpec()); + launch_info.SetShellExpandArguments( + m_expand_shell_arguments_field->GetBoolean()); + } + + void GetStandardIO(ProcessLaunchInfo &launch_info) { + if (m_disable_standard_io_field->GetBoolean()) { + launch_info.GetFlags().Set(eLaunchFlagDisableSTDIO); + return; + } + + FileAction action; + if (m_standard_input_field->IsSpecified()) { + if (action.Open(STDIN_FILENO, m_standard_input_field->GetFileSpec(), true, + false)) + launch_info.AppendFileAction(action); + } + if (m_standard_output_field->IsSpecified()) { + if (action.Open(STDOUT_FILENO, m_standard_output_field->GetFileSpec(), + false, true)) + launch_info.AppendFileAction(action); + } + if (m_standard_error_field->IsSpecified()) { + if (action.Open(STDERR_FILENO, m_standard_error_field->GetFileSpec(), + false, true)) + launch_info.AppendFileAction(action); + } + } + + void GetInheritTCC(ProcessLaunchInfo &launch_info) { + if (m_debugger.GetSelectedTarget()->GetInheritTCC()) + launch_info.GetFlags().Set(eLaunchFlagInheritTCCFromParent); + } + + ProcessLaunchInfo GetLaunchInfo() { + ProcessLaunchInfo launch_info; + + GetExecutableSettings(launch_info); + GetArguments(launch_info); + GetEnvironment(launch_info); + GetWorkingDirectory(launch_info); + GetStopAtEntry(launch_info); + GetDetachOnError(launch_info); + GetDisableASLR(launch_info); + GetPlugin(launch_info); + GetArch(launch_info); + GetShell(launch_info); + GetStandardIO(launch_info); + GetInheritTCC(launch_info); + + return launch_info; + } + + bool StopRunningProcess() { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + + if (!exe_ctx.HasProcessScope()) + return false; + + Process *process = exe_ctx.GetProcessPtr(); + if (!(process && process->IsAlive())) + return false; + + FormDelegateSP form_delegate_sp = + FormDelegateSP(new DetachOrKillProcessFormDelegate(process)); + Rect bounds = m_main_window_sp->GetCenteredRect(85, 8); + WindowSP form_window_sp = m_main_window_sp->CreateSubWindow( + form_delegate_sp->GetName().c_str(), bounds, true); + WindowDelegateSP window_delegate_sp = + WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); + form_window_sp->SetDelegate(window_delegate_sp); + + return true; + } + + Target *GetTarget() { + Target *target = m_debugger.GetSelectedTarget().get(); + + if (target == nullptr) { + SetError("No target exists!"); + return nullptr; + } + + ModuleSP exe_module_sp = target->GetExecutableModule(); + + if (exe_module_sp == nullptr) { + SetError("No executable in target!"); + return nullptr; + } + + return target; + } + + void Launch(Window &window) { + ClearError(); + + bool all_fields_are_valid = CheckFieldsValidity(); + if (!all_fields_are_valid) + return; + + bool process_is_running = StopRunningProcess(); + if (process_is_running) + return; + + Target *target = GetTarget(); + if (HasError()) + return; + + StreamString stream; + ProcessLaunchInfo launch_info = GetLaunchInfo(); + Status status = target->Launch(launch_info, &stream); + + if (status.Fail()) { + SetError(status.AsCString()); + return; + } + + ProcessSP process_sp(target->GetProcessSP()); + if (!process_sp) { + SetError("Launched successfully but target has no process!"); + return; + } + + window.GetParent()->RemoveSubWindow(&window); + } + +protected: + Debugger &m_debugger; + WindowSP m_main_window_sp; + + ArgumentsFieldDelegate *m_arguments_field; + EnvironmentVariableListFieldDelegate *m_target_environment_field; + DirectoryFieldDelegate *m_working_directory_field; + + BooleanFieldDelegate *m_show_advanced_field; + + BooleanFieldDelegate *m_stop_at_entry_field; + BooleanFieldDelegate *m_detach_on_error_field; + BooleanFieldDelegate *m_disable_aslr_field; + ProcessPluginFieldDelegate *m_plugin_field; + ArchFieldDelegate *m_arch_field; + FileFieldDelegate *m_shell_field; + BooleanFieldDelegate *m_expand_shell_arguments_field; + BooleanFieldDelegate *m_disable_standard_io_field; + FileFieldDelegate *m_standard_input_field; + FileFieldDelegate *m_standard_output_field; + FileFieldDelegate *m_standard_error_field; + + BooleanFieldDelegate *m_show_inherited_environment_field; + EnvironmentVariableListFieldDelegate *m_inherited_environment_field; +}; + +//////////// +// Searchers +//////////// + +class SearcherDelegate { +public: + SearcherDelegate() = default; + + virtual ~SearcherDelegate() = default; + + virtual int GetNumberOfMatches() = 0; + + // Get the string that will be displayed for the match at the input index. + virtual const std::string &GetMatchTextAtIndex(int index) = 0; + + // Update the matches of the search. This is executed every time the text + // field handles an event. + virtual void UpdateMatches(const std::string &text) = 0; + + // Execute the user callback given the index of some match. This is executed + // once the user selects a match. + virtual void ExecuteCallback(int match_index) = 0; +}; + +typedef std::shared_ptr<SearcherDelegate> SearcherDelegateSP; + +class SearcherWindowDelegate : public WindowDelegate { +public: + SearcherWindowDelegate(SearcherDelegateSP &delegate_sp) + : m_delegate_sp(delegate_sp), m_text_field("Search", "", false) { + ; + } + + // A completion window is padded by one character from all sides. A text field + // is first drawn for inputting the searcher request, then a list of matches + // are displayed in a scrollable list. + // + // ___<Searcher Window Name>____________________________ + // | | + // | __[Search]_______________________________________ | + // | | | | + // | |_______________________________________________| | + // | - Match 1. | + // | - Match 2. | + // | - ... | + // | | + // |____________________________[Press Esc to Cancel]__| + // + + // Get the index of the last visible match. Assuming at least one match + // exists. + int GetLastVisibleMatch(int height) { + int index = m_first_visible_match + height; + return std::min(index, m_delegate_sp->GetNumberOfMatches()) - 1; + } + + int GetNumberOfVisibleMatches(int height) { + return GetLastVisibleMatch(height) - m_first_visible_match + 1; + } + + void UpdateScrolling(Surface &surface) { + if (m_selected_match < m_first_visible_match) { + m_first_visible_match = m_selected_match; + return; + } + + int height = surface.GetHeight(); + int last_visible_match = GetLastVisibleMatch(height); + if (m_selected_match > last_visible_match) { + m_first_visible_match = m_selected_match - height + 1; + } + } + + void DrawMatches(Surface &surface) { + if (m_delegate_sp->GetNumberOfMatches() == 0) + return; + + UpdateScrolling(surface); + + int count = GetNumberOfVisibleMatches(surface.GetHeight()); + for (int i = 0; i < count; i++) { + surface.MoveCursor(1, i); + int current_match = m_first_visible_match + i; + if (current_match == m_selected_match) + surface.AttributeOn(A_REVERSE); + surface.PutCString( + m_delegate_sp->GetMatchTextAtIndex(current_match).c_str()); + if (current_match == m_selected_match) + surface.AttributeOff(A_REVERSE); + } + } + + void DrawContent(Surface &surface) { + Rect content_bounds = surface.GetFrame(); + Rect text_field_bounds, matchs_bounds; + content_bounds.HorizontalSplit(m_text_field.FieldDelegateGetHeight(), + text_field_bounds, matchs_bounds); + Surface text_field_surface = surface.SubSurface(text_field_bounds); + Surface matches_surface = surface.SubSurface(matchs_bounds); + + m_text_field.FieldDelegateDraw(text_field_surface, true); + DrawMatches(matches_surface); + } + + bool WindowDelegateDraw(Window &window, bool force) override { + window.Erase(); + + window.DrawTitleBox(window.GetName(), "Press Esc to Cancel"); + + Rect content_bounds = window.GetFrame(); + content_bounds.Inset(2, 2); + Surface content_surface = window.SubSurface(content_bounds); + + DrawContent(content_surface); + return true; + } + + void SelectNext() { + if (m_selected_match != m_delegate_sp->GetNumberOfMatches() - 1) + m_selected_match++; + } + + void SelectPrevious() { + if (m_selected_match != 0) + m_selected_match--; + } + + void ExecuteCallback(Window &window) { + m_delegate_sp->ExecuteCallback(m_selected_match); + window.GetParent()->RemoveSubWindow(&window); + } + + void UpdateMatches() { + m_delegate_sp->UpdateMatches(m_text_field.GetText()); + m_selected_match = 0; + } + + HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { + switch (key) { + case '\r': + case '\n': + case KEY_ENTER: + ExecuteCallback(window); + return eKeyHandled; + case '\t': + case KEY_DOWN: + SelectNext(); + return eKeyHandled; + case KEY_SHIFT_TAB: + case KEY_UP: + SelectPrevious(); + return eKeyHandled; + case KEY_ESCAPE: + window.GetParent()->RemoveSubWindow(&window); + return eKeyHandled; + default: + break; + } + + if (m_text_field.FieldDelegateHandleChar(key) == eKeyHandled) + UpdateMatches(); + + return eKeyHandled; + } + +protected: + SearcherDelegateSP m_delegate_sp; + TextFieldDelegate m_text_field; + // The index of the currently selected match. + int m_selected_match = 0; + // The index of the first visible match. + int m_first_visible_match = 0; +}; + +////////////////////////////// +// Searcher Delegate Instances +////////////////////////////// + +// This is a searcher delegate wrapper around CommandCompletions common +// callbacks. The callbacks are only given the match string. The completion_mask +// can be a combination of lldb::CompletionType. +class CommonCompletionSearcherDelegate : public SearcherDelegate { +public: + typedef std::function<void(const std::string &)> CallbackType; + + CommonCompletionSearcherDelegate(Debugger &debugger, uint32_t completion_mask, + CallbackType callback) + : m_debugger(debugger), m_completion_mask(completion_mask), + m_callback(callback) {} + + int GetNumberOfMatches() override { return m_matches.GetSize(); } + + const std::string &GetMatchTextAtIndex(int index) override { + return m_matches[index]; + } + + void UpdateMatches(const std::string &text) override { + CompletionResult result; + CompletionRequest request(text.c_str(), text.size(), result); + lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks( + m_debugger.GetCommandInterpreter(), m_completion_mask, request, + nullptr); + result.GetMatches(m_matches); + } + + void ExecuteCallback(int match_index) override { + m_callback(m_matches[match_index]); + } + +protected: + Debugger &m_debugger; + // A compound mask from lldb::CompletionType. + uint32_t m_completion_mask; + // A callback to execute once the user selects a match. The match is passed to + // the callback as a string. + CallbackType m_callback; + StringList m_matches; +}; + +//////// +// Menus +//////// + +class MenuDelegate { +public: + virtual ~MenuDelegate() = default; + + virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0; +}; + +class Menu : public WindowDelegate { +public: + enum class Type { Invalid, Bar, Item, Separator }; + + // Menubar or separator constructor + Menu(Type type); + + // Menuitem constructor + Menu(const char *name, const char *key_name, int key_value, + uint64_t identifier); + + ~Menu() override = default; + + const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; } + + void SetDelegate(const MenuDelegateSP &delegate_sp) { + m_delegate_sp = delegate_sp; + } + + void RecalculateNameLengths(); + + void AddSubmenu(const MenuSP &menu_sp); + + int DrawAndRunMenu(Window &window); + + void DrawMenuTitle(Window &window, bool highlight); + + bool WindowDelegateDraw(Window &window, bool force) override; + + HandleCharResult WindowDelegateHandleChar(Window &window, int key) override; + + MenuActionResult ActionPrivate(Menu &menu) { + MenuActionResult result = MenuActionResult::NotHandled; + if (m_delegate_sp) { + result = m_delegate_sp->MenuDelegateAction(menu); + if (result != MenuActionResult::NotHandled) + return result; + } else if (m_parent) { + result = m_parent->ActionPrivate(menu); + if (result != MenuActionResult::NotHandled) + return result; + } + return m_canned_result; + } + + MenuActionResult Action() { + // Call the recursive action so it can try to handle it with the menu + // delegate, and if not, try our parent menu + return ActionPrivate(*this); + } + + void SetCannedResult(MenuActionResult result) { m_canned_result = result; } + + Menus &GetSubmenus() { return m_submenus; } + + const Menus &GetSubmenus() const { return m_submenus; } + + int GetSelectedSubmenuIndex() const { return m_selected; } + + void SetSelectedSubmenuIndex(int idx) { m_selected = idx; } + + Type GetType() const { return m_type; } + + int GetStartingColumn() const { return m_start_col; } + + void SetStartingColumn(int col) { m_start_col = col; } + + int GetKeyValue() const { return m_key_value; } + + std::string &GetName() { return m_name; } + + int GetDrawWidth() const { + return m_max_submenu_name_length + m_max_submenu_key_name_length + 8; + } + + uint64_t GetIdentifier() const { return m_identifier; } + + void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } + +protected: + std::string m_name; + std::string m_key_name; + uint64_t m_identifier; + Type m_type; + int m_key_value; + int m_start_col; + int m_max_submenu_name_length; + int m_max_submenu_key_name_length; + int m_selected; + Menu *m_parent; + Menus m_submenus; + WindowSP m_menu_window_sp; + MenuActionResult m_canned_result; + MenuDelegateSP m_delegate_sp; +}; + +// Menubar or separator constructor +Menu::Menu(Type type) + : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0), + m_start_col(0), m_max_submenu_name_length(0), + m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr), + m_submenus(), m_canned_result(MenuActionResult::NotHandled), + m_delegate_sp() {} + +// Menuitem constructor +Menu::Menu(const char *name, const char *key_name, int key_value, + uint64_t identifier) + : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid), + m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0), + m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr), + m_submenus(), m_canned_result(MenuActionResult::NotHandled), + m_delegate_sp() { + if (name && name[0]) { + m_name = name; + m_type = Type::Item; + if (key_name && key_name[0]) + m_key_name = key_name; + } else { + m_type = Type::Separator; + } +} + +void Menu::RecalculateNameLengths() { + m_max_submenu_name_length = 0; + m_max_submenu_key_name_length = 0; + Menus &submenus = GetSubmenus(); + const size_t num_submenus = submenus.size(); + for (size_t i = 0; i < num_submenus; ++i) { + Menu *submenu = submenus[i].get(); + if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size()) + m_max_submenu_name_length = submenu->m_name.size(); + if (static_cast<size_t>(m_max_submenu_key_name_length) < + submenu->m_key_name.size()) + m_max_submenu_key_name_length = submenu->m_key_name.size(); + } +} + +void Menu::AddSubmenu(const MenuSP &menu_sp) { + menu_sp->m_parent = this; + if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size()) + m_max_submenu_name_length = menu_sp->m_name.size(); + if (static_cast<size_t>(m_max_submenu_key_name_length) < + menu_sp->m_key_name.size()) + m_max_submenu_key_name_length = menu_sp->m_key_name.size(); + m_submenus.push_back(menu_sp); +} + +void Menu::DrawMenuTitle(Window &window, bool highlight) { + if (m_type == Type::Separator) { + window.MoveCursor(0, window.GetCursorY()); + window.PutChar(ACS_LTEE); + int width = window.GetWidth(); + if (width > 2) { + width -= 2; + for (int i = 0; i < width; ++i) + window.PutChar(ACS_HLINE); + } + window.PutChar(ACS_RTEE); + } else { + const int shortcut_key = m_key_value; + bool underlined_shortcut = false; + const attr_t highlight_attr = A_REVERSE; + if (highlight) + window.AttributeOn(highlight_attr); + if (llvm::isPrint(shortcut_key)) { + size_t lower_pos = m_name.find(tolower(shortcut_key)); + size_t upper_pos = m_name.find(toupper(shortcut_key)); + const char *name = m_name.c_str(); + size_t pos = std::min<size_t>(lower_pos, upper_pos); + if (pos != std::string::npos) { + underlined_shortcut = true; + if (pos > 0) { + window.PutCString(name, pos); + name += pos; + } + const attr_t shortcut_attr = A_UNDERLINE | A_BOLD; + window.AttributeOn(shortcut_attr); + window.PutChar(name[0]); + window.AttributeOff(shortcut_attr); + name++; + if (name[0]) + window.PutCString(name); + } + } + + if (!underlined_shortcut) { + window.PutCString(m_name.c_str()); + } + + if (highlight) + window.AttributeOff(highlight_attr); + + if (m_key_name.empty()) { + if (!underlined_shortcut && llvm::isPrint(m_key_value)) { + window.AttributeOn(COLOR_PAIR(MagentaOnWhite)); + window.Printf(" (%c)", m_key_value); + window.AttributeOff(COLOR_PAIR(MagentaOnWhite)); + } + } else { + window.AttributeOn(COLOR_PAIR(MagentaOnWhite)); + window.Printf(" (%s)", m_key_name.c_str()); + window.AttributeOff(COLOR_PAIR(MagentaOnWhite)); + } + } +} + +bool Menu::WindowDelegateDraw(Window &window, bool force) { + Menus &submenus = GetSubmenus(); + const size_t num_submenus = submenus.size(); + const int selected_idx = GetSelectedSubmenuIndex(); + Menu::Type menu_type = GetType(); + switch (menu_type) { + case Menu::Type::Bar: { + window.SetBackground(BlackOnWhite); + window.MoveCursor(0, 0); + for (size_t i = 0; i < num_submenus; ++i) { + Menu *menu = submenus[i].get(); + if (i > 0) + window.PutChar(' '); + menu->SetStartingColumn(window.GetCursorX()); + window.PutCString("| "); + menu->DrawMenuTitle(window, false); + } + window.PutCString(" |"); + } break; + + case Menu::Type::Item: { + int y = 1; + int x = 3; + // Draw the menu + int cursor_x = 0; + int cursor_y = 0; + window.Erase(); + window.SetBackground(BlackOnWhite); + window.Box(); + for (size_t i = 0; i < num_submenus; ++i) { + const bool is_selected = (i == static_cast<size_t>(selected_idx)); + window.MoveCursor(x, y + i); + if (is_selected) { + // Remember where we want the cursor to be + cursor_x = x - 1; + cursor_y = y + i; + } + submenus[i]->DrawMenuTitle(window, is_selected); + } + window.MoveCursor(cursor_x, cursor_y); + } break; + + default: + case Menu::Type::Separator: + break; + } + return true; // Drawing handled... +} + +HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) { + HandleCharResult result = eKeyNotHandled; + + Menus &submenus = GetSubmenus(); + const size_t num_submenus = submenus.size(); + const int selected_idx = GetSelectedSubmenuIndex(); + Menu::Type menu_type = GetType(); + if (menu_type == Menu::Type::Bar) { + MenuSP run_menu_sp; + switch (key) { + case KEY_DOWN: + case KEY_UP: + // Show last menu or first menu + if (selected_idx < static_cast<int>(num_submenus)) + run_menu_sp = submenus[selected_idx]; + else if (!submenus.empty()) + run_menu_sp = submenus.front(); + result = eKeyHandled; + break; + + case KEY_RIGHT: + ++m_selected; + if (m_selected >= static_cast<int>(num_submenus)) + m_selected = 0; + if (m_selected < static_cast<int>(num_submenus)) + run_menu_sp = submenus[m_selected]; + else if (!submenus.empty()) + run_menu_sp = submenus.front(); + result = eKeyHandled; + break; + + case KEY_LEFT: + --m_selected; + if (m_selected < 0) + m_selected = num_submenus - 1; + if (m_selected < static_cast<int>(num_submenus)) + run_menu_sp = submenus[m_selected]; + else if (!submenus.empty()) + run_menu_sp = submenus.front(); + result = eKeyHandled; + break; + + default: + for (size_t i = 0; i < num_submenus; ++i) { + if (submenus[i]->GetKeyValue() == key) { + SetSelectedSubmenuIndex(i); + run_menu_sp = submenus[i]; + result = eKeyHandled; + break; + } + } + break; + } + + if (run_menu_sp) { + // Run the action on this menu in case we need to populate the menu with + // dynamic content and also in case check marks, and any other menu + // decorations need to be calculated + if (run_menu_sp->Action() == MenuActionResult::Quit) + return eQuitApplication; + + Rect menu_bounds; + menu_bounds.origin.x = run_menu_sp->GetStartingColumn(); + menu_bounds.origin.y = 1; + menu_bounds.size.width = run_menu_sp->GetDrawWidth(); + menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2; + if (m_menu_window_sp) + window.GetParent()->RemoveSubWindow(m_menu_window_sp.get()); + + m_menu_window_sp = window.GetParent()->CreateSubWindow( + run_menu_sp->GetName().c_str(), menu_bounds, true); + m_menu_window_sp->SetDelegate(run_menu_sp); + } + } else if (menu_type == Menu::Type::Item) { + switch (key) { + case KEY_DOWN: + if (m_submenus.size() > 1) { + const int start_select = m_selected; + while (++m_selected != start_select) { + if (static_cast<size_t>(m_selected) >= num_submenus) + m_selected = 0; + if (m_submenus[m_selected]->GetType() == Type::Separator) + continue; + else + break; + } + return eKeyHandled; + } + break; + + case KEY_UP: + if (m_submenus.size() > 1) { + const int start_select = m_selected; + while (--m_selected != start_select) { + if (m_selected < static_cast<int>(0)) + m_selected = num_submenus - 1; + if (m_submenus[m_selected]->GetType() == Type::Separator) + continue; + else + break; + } + return eKeyHandled; + } + break; + + case KEY_RETURN: + if (static_cast<size_t>(selected_idx) < num_submenus) { + if (submenus[selected_idx]->Action() == MenuActionResult::Quit) + return eQuitApplication; + window.GetParent()->RemoveSubWindow(&window); + return eKeyHandled; + } + break; + + case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in + // case other chars are entered for escaped sequences + window.GetParent()->RemoveSubWindow(&window); + return eKeyHandled; + + default: + for (size_t i = 0; i < num_submenus; ++i) { + Menu *menu = submenus[i].get(); + if (menu->GetKeyValue() == key) { + SetSelectedSubmenuIndex(i); + window.GetParent()->RemoveSubWindow(&window); + if (menu->Action() == MenuActionResult::Quit) + return eQuitApplication; + return eKeyHandled; + } + } + break; + } + } else if (menu_type == Menu::Type::Separator) { + } + return result; +} + +class Application { +public: + Application(FILE *in, FILE *out) : m_window_sp(), m_in(in), m_out(out) {} + + ~Application() { + m_window_delegates.clear(); + m_window_sp.reset(); + if (m_screen) { + ::delscreen(m_screen); + m_screen = nullptr; + } + } + + void Initialize() { + m_screen = ::newterm(nullptr, m_out, m_in); + ::start_color(); + ::curs_set(0); + ::noecho(); + ::keypad(stdscr, TRUE); + } + + void Terminate() { ::endwin(); } + + void Run(Debugger &debugger) { + bool done = false; + int delay_in_tenths_of_a_second = 1; + + // Alas the threading model in curses is a bit lame so we need to resort + // to polling every 0.5 seconds. We could poll for stdin ourselves and + // then pass the keys down but then we need to translate all of the escape + // sequences ourselves. So we resort to polling for input because we need + // to receive async process events while in this loop. + + halfdelay(delay_in_tenths_of_a_second); // Poll using some number of + // tenths of seconds seconds when + // calling Window::GetChar() + + ListenerSP listener_sp( + Listener::MakeListener("lldb.IOHandler.curses.Application")); + ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass()); + debugger.EnableForwardEvents(listener_sp); + + m_update_screen = true; +#if defined(__APPLE__) + std::deque<int> escape_chars; +#endif + + while (!done) { + if (m_update_screen) { + m_window_sp->Draw(false); + // All windows should be calling Window::DeferredRefresh() instead of + // Window::Refresh() so we can do a single update and avoid any screen + // blinking + update_panels(); + + // Cursor hiding isn't working on MacOSX, so hide it in the top left + // corner + m_window_sp->MoveCursor(0, 0); + + doupdate(); + m_update_screen = false; + } + +#if defined(__APPLE__) + // Terminal.app doesn't map its function keys correctly, F1-F4 default + // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if + // possible + int ch; + if (escape_chars.empty()) + ch = m_window_sp->GetChar(); + else { + ch = escape_chars.front(); + escape_chars.pop_front(); + } + if (ch == KEY_ESCAPE) { + int ch2 = m_window_sp->GetChar(); + if (ch2 == 'O') { + int ch3 = m_window_sp->GetChar(); + switch (ch3) { + case 'P': + ch = KEY_F(1); + break; + case 'Q': + ch = KEY_F(2); + break; + case 'R': + ch = KEY_F(3); + break; + case 'S': + ch = KEY_F(4); + break; + default: + escape_chars.push_back(ch2); + if (ch3 != -1) + escape_chars.push_back(ch3); + break; + } + } else if (ch2 != -1) + escape_chars.push_back(ch2); + } +#else + int ch = m_window_sp->GetChar(); + +#endif + if (ch == -1) { + if (feof(m_in) || ferror(m_in)) { + done = true; + } else { + // Just a timeout from using halfdelay(), check for events + EventSP event_sp; + while (listener_sp->PeekAtNextEvent()) { + listener_sp->GetEvent(event_sp, std::chrono::seconds(0)); + + if (event_sp) { + Broadcaster *broadcaster = event_sp->GetBroadcaster(); + if (broadcaster) { + // uint32_t event_type = event_sp->GetType(); + ConstString broadcaster_class( + broadcaster->GetBroadcasterClass()); + if (broadcaster_class == broadcaster_class_process) { + m_update_screen = true; + continue; // Don't get any key, just update our view + } + } + } + } + } + } else { + HandleCharResult key_result = m_window_sp->HandleChar(ch); + switch (key_result) { + case eKeyHandled: + m_update_screen = true; + break; + case eKeyNotHandled: + if (ch == 12) { // Ctrl+L, force full redraw + redrawwin(m_window_sp->get()); + m_update_screen = true; + } + break; + case eQuitApplication: + done = true; + break; + } + } + } + + debugger.CancelForwardEvents(listener_sp); + } + + WindowSP &GetMainWindow() { + if (!m_window_sp) + m_window_sp = std::make_shared<Window>("main", stdscr, false); + return m_window_sp; + } + + void TerminalSizeChanged() { + ::endwin(); + ::refresh(); + Rect content_bounds = m_window_sp->GetFrame(); + m_window_sp->SetBounds(content_bounds); + if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar")) + menubar_window_sp->SetBounds(content_bounds.MakeMenuBar()); + if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status")) + status_window_sp->SetBounds(content_bounds.MakeStatusBar()); + + WindowSP source_window_sp = m_window_sp->FindSubWindow("Source"); + WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables"); + WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers"); + WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads"); + + Rect threads_bounds; + Rect source_variables_bounds; + content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, + threads_bounds); + if (threads_window_sp) + threads_window_sp->SetBounds(threads_bounds); + else + source_variables_bounds = content_bounds; + + Rect source_bounds; + Rect variables_registers_bounds; + source_variables_bounds.HorizontalSplitPercentage( + 0.70, source_bounds, variables_registers_bounds); + if (variables_window_sp || registers_window_sp) { + if (variables_window_sp && registers_window_sp) { + Rect variables_bounds; + Rect registers_bounds; + variables_registers_bounds.VerticalSplitPercentage( + 0.50, variables_bounds, registers_bounds); + variables_window_sp->SetBounds(variables_bounds); + registers_window_sp->SetBounds(registers_bounds); + } else if (variables_window_sp) { + variables_window_sp->SetBounds(variables_registers_bounds); + } else { + registers_window_sp->SetBounds(variables_registers_bounds); + } + } else { + source_bounds = source_variables_bounds; + } + + source_window_sp->SetBounds(source_bounds); + + touchwin(stdscr); + redrawwin(m_window_sp->get()); + m_update_screen = true; + } + +protected: + WindowSP m_window_sp; + WindowDelegates m_window_delegates; + SCREEN *m_screen = nullptr; + FILE *m_in; + FILE *m_out; + bool m_update_screen = false; +}; + +} // namespace curses + +using namespace curses; + +struct Row { + ValueObjectUpdater value; + Row *parent; + // The process stop ID when the children were calculated. + uint32_t children_stop_id = 0; + int row_idx = 0; + int x = 1; + int y = 1; + bool might_have_children; + bool expanded = false; + bool calculated_children = false; + std::vector<Row> children; + + Row(const ValueObjectSP &v, Row *p) + : value(v), parent(p), + might_have_children(v ? v->MightHaveChildren() : false) {} + + size_t GetDepth() const { + if (parent) + return 1 + parent->GetDepth(); + return 0; + } + + void Expand() { expanded = true; } + + std::vector<Row> &GetChildren() { + ProcessSP process_sp = value.GetProcessSP(); + auto stop_id = process_sp->GetStopID(); + if (process_sp && stop_id != children_stop_id) { + children_stop_id = stop_id; + calculated_children = false; + } + if (!calculated_children) { + children.clear(); + calculated_children = true; + ValueObjectSP valobj = value.GetSP(); + if (valobj) { + const uint32_t num_children = valobj->GetNumChildrenIgnoringErrors(); + for (size_t i = 0; i < num_children; ++i) { + children.push_back(Row(valobj->GetChildAtIndex(i), this)); + } + } + } + return children; + } + + void Unexpand() { + expanded = false; + calculated_children = false; + children.clear(); + } + + void DrawTree(Window &window) { + if (parent) + parent->DrawTreeForChild(window, this, 0); + + if (might_have_children && + (!calculated_children || !GetChildren().empty())) { + // It we can get UTF8 characters to work we should try to use the + // "symbol" UTF8 string below + // const char *symbol = ""; + // if (row.expanded) + // symbol = "\xe2\x96\xbd "; + // else + // symbol = "\xe2\x96\xb7 "; + // window.PutCString (symbol); + + // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v' + // or '>' character... + // if (expanded) + // window.PutChar (ACS_DARROW); + // else + // window.PutChar (ACS_RARROW); + // Since we can't find any good looking right arrow/down arrow symbols, + // just use a diamond... + window.PutChar(ACS_DIAMOND); + window.PutChar(ACS_HLINE); + } + } + + void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) { + if (parent) + parent->DrawTreeForChild(window, this, reverse_depth + 1); + + if (&GetChildren().back() == child) { + // Last child + if (reverse_depth == 0) { + window.PutChar(ACS_LLCORNER); + window.PutChar(ACS_HLINE); + } else { + window.PutChar(' '); + window.PutChar(' '); + } + } else { + if (reverse_depth == 0) { + window.PutChar(ACS_LTEE); + window.PutChar(ACS_HLINE); + } else { + window.PutChar(ACS_VLINE); + window.PutChar(' '); + } + } + } +}; + +struct DisplayOptions { + bool show_types; +}; + +class TreeItem; + +class TreeDelegate { +public: + TreeDelegate() = default; + virtual ~TreeDelegate() = default; + + virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0; + virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0; + virtual void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index, + TreeItem *&selected_item) {} + // This is invoked when a tree item is selected. If true is returned, the + // views are updated. + virtual bool TreeDelegateItemSelected(TreeItem &item) = 0; + virtual bool TreeDelegateExpandRootByDefault() { return false; } + // This is mostly useful for root tree delegates. If false is returned, + // drawing will be skipped completely. This is needed, for instance, in + // skipping drawing of the threads tree if there is no running process. + virtual bool TreeDelegateShouldDraw() { return true; } +}; + +typedef std::shared_ptr<TreeDelegate> TreeDelegateSP; + +struct TreeItemData { + TreeItemData(TreeItem *parent, TreeDelegate &delegate, + bool might_have_children, bool is_expanded) + : m_parent(parent), m_delegate(&delegate), + m_might_have_children(might_have_children), m_is_expanded(is_expanded) { + } + +protected: + TreeItem *m_parent; + TreeDelegate *m_delegate; + void *m_user_data = nullptr; + uint64_t m_identifier = 0; + std::string m_text; + int m_row_idx = -1; // Zero based visible row index, -1 if not visible or for + // the root item + bool m_might_have_children; + bool m_is_expanded = false; +}; + +class TreeItem : public TreeItemData { +public: + TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children) + : TreeItemData(parent, delegate, might_have_children, + parent == nullptr + ? delegate.TreeDelegateExpandRootByDefault() + : false), + m_children() {} + + TreeItem(const TreeItem &) = delete; + TreeItem &operator=(const TreeItem &rhs) = delete; + + TreeItem &operator=(TreeItem &&rhs) { + if (this != &rhs) { + TreeItemData::operator=(std::move(rhs)); + AdoptChildren(rhs.m_children); + } + return *this; + } + + TreeItem(TreeItem &&rhs) : TreeItemData(std::move(rhs)) { + AdoptChildren(rhs.m_children); + } + + size_t GetDepth() const { + if (m_parent) + return 1 + m_parent->GetDepth(); + return 0; + } + + int GetRowIndex() const { return m_row_idx; } + + void ClearChildren() { m_children.clear(); } + + void Resize(size_t n, TreeDelegate &delegate, bool might_have_children) { + if (m_children.size() >= n) { + m_children.erase(m_children.begin() + n, m_children.end()); + return; + } + m_children.reserve(n); + std::generate_n(std::back_inserter(m_children), n - m_children.size(), + [&, parent = this]() { + return TreeItem(parent, delegate, might_have_children); + }); + } + + TreeItem &operator[](size_t i) { return m_children[i]; } + + void SetRowIndex(int row_idx) { m_row_idx = row_idx; } + + size_t GetNumChildren() { + m_delegate->TreeDelegateGenerateChildren(*this); + return m_children.size(); + } + + void ItemWasSelected() { m_delegate->TreeDelegateItemSelected(*this); } + + void CalculateRowIndexes(int &row_idx) { + SetRowIndex(row_idx); + ++row_idx; + + const bool expanded = IsExpanded(); + + // The root item must calculate its children, or we must calculate the + // number of children if the item is expanded + if (m_parent == nullptr || expanded) + GetNumChildren(); + + for (auto &item : m_children) { + if (expanded) + item.CalculateRowIndexes(row_idx); + else + item.SetRowIndex(-1); + } + } + + TreeItem *GetParent() { return m_parent; } + + bool IsExpanded() const { return m_is_expanded; } + + void Expand() { m_is_expanded = true; } + + void Unexpand() { m_is_expanded = false; } + + bool Draw(Window &window, const int first_visible_row, + const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) { + if (num_rows_left <= 0) + return false; + + if (m_row_idx >= first_visible_row) { + window.MoveCursor(2, row_idx + 1); + + if (m_parent) + m_parent->DrawTreeForChild(window, this, 0); + + if (m_might_have_children) { + // It we can get UTF8 characters to work we should try to use the + // "symbol" UTF8 string below + // const char *symbol = ""; + // if (row.expanded) + // symbol = "\xe2\x96\xbd "; + // else + // symbol = "\xe2\x96\xb7 "; + // window.PutCString (symbol); + + // The ACS_DARROW and ACS_RARROW don't look very nice they are just a + // 'v' or '>' character... + // if (expanded) + // window.PutChar (ACS_DARROW); + // else + // window.PutChar (ACS_RARROW); + // Since we can't find any good looking right arrow/down arrow symbols, + // just use a diamond... + window.PutChar(ACS_DIAMOND); + window.PutChar(ACS_HLINE); + } + bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) && + window.IsActive(); + + if (highlight) + window.AttributeOn(A_REVERSE); + + m_delegate->TreeDelegateDrawTreeItem(*this, window); + + if (highlight) + window.AttributeOff(A_REVERSE); + ++row_idx; + --num_rows_left; + } + + if (num_rows_left <= 0) + return false; // We are done drawing... + + if (IsExpanded()) { + for (auto &item : m_children) { + // If we displayed all the rows and item.Draw() returns false we are + // done drawing and can exit this for loop + if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx, + num_rows_left)) + break; + } + } + return num_rows_left >= 0; // Return true if not done drawing yet + } + + void DrawTreeForChild(Window &window, TreeItem *child, + uint32_t reverse_depth) { + if (m_parent) + m_parent->DrawTreeForChild(window, this, reverse_depth + 1); + + if (&m_children.back() == child) { + // Last child + if (reverse_depth == 0) { + window.PutChar(ACS_LLCORNER); + window.PutChar(ACS_HLINE); + } else { + window.PutChar(' '); + window.PutChar(' '); + } + } else { + if (reverse_depth == 0) { + window.PutChar(ACS_LTEE); + window.PutChar(ACS_HLINE); + } else { + window.PutChar(ACS_VLINE); + window.PutChar(' '); + } + } + } + + TreeItem *GetItemForRowIndex(uint32_t row_idx) { + if (static_cast<uint32_t>(m_row_idx) == row_idx) + return this; + if (m_children.empty()) + return nullptr; + if (IsExpanded()) { + for (auto &item : m_children) { + TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx); + if (selected_item_ptr) + return selected_item_ptr; + } + } + return nullptr; + } + + void *GetUserData() const { return m_user_data; } + + void SetUserData(void *user_data) { m_user_data = user_data; } + + uint64_t GetIdentifier() const { return m_identifier; } + + void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } + + const std::string &GetText() const { return m_text; } + + void SetText(const char *text) { + if (text == nullptr) { + m_text.clear(); + return; + } + m_text = text; + } + + void SetMightHaveChildren(bool b) { m_might_have_children = b; } + +protected: + void AdoptChildren(std::vector<TreeItem> &children) { + m_children = std::move(children); + for (auto &child : m_children) + child.m_parent = this; + } + + std::vector<TreeItem> m_children; +}; + +class TreeWindowDelegate : public WindowDelegate { +public: + TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp) + : m_debugger(debugger), m_delegate_sp(delegate_sp), + m_root(nullptr, *delegate_sp, true) {} + + int NumVisibleRows() const { return m_max_y - m_min_y; } + + bool WindowDelegateDraw(Window &window, bool force) override { + m_min_x = 2; + m_min_y = 1; + m_max_x = window.GetWidth() - 1; + m_max_y = window.GetHeight() - 1; + + window.Erase(); + window.DrawTitleBox(window.GetName()); + + if (!m_delegate_sp->TreeDelegateShouldDraw()) { + m_selected_item = nullptr; + return true; + } + + const int num_visible_rows = NumVisibleRows(); + m_num_rows = 0; + m_root.CalculateRowIndexes(m_num_rows); + m_delegate_sp->TreeDelegateUpdateSelection(m_root, m_selected_row_idx, + m_selected_item); + + // If we unexpanded while having something selected our total number of + // rows is less than the num visible rows, then make sure we show all the + // rows by setting the first visible row accordingly. + if (m_first_visible_row > 0 && m_num_rows < num_visible_rows) + m_first_visible_row = 0; + + // Make sure the selected row is always visible + if (m_selected_row_idx < m_first_visible_row) + m_first_visible_row = m_selected_row_idx; + else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) + m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; + + int row_idx = 0; + int num_rows_left = num_visible_rows; + m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx, + num_rows_left); + // Get the selected row + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + + return true; // Drawing handled + } + + const char *WindowDelegateGetHelpText() override { + return "Thread window keyboard shortcuts:"; + } + + KeyHelp *WindowDelegateGetKeyHelp() override { + static curses::KeyHelp g_source_view_key_help[] = { + {KEY_UP, "Select previous item"}, + {KEY_DOWN, "Select next item"}, + {KEY_RIGHT, "Expand the selected item"}, + {KEY_LEFT, + "Unexpand the selected item or select parent if not expanded"}, + {KEY_PPAGE, "Page up"}, + {KEY_NPAGE, "Page down"}, + {'h', "Show help dialog"}, + {' ', "Toggle item expansion"}, + {',', "Page up"}, + {'.', "Page down"}, + {'\0', nullptr}}; + return g_source_view_key_help; + } + + HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { + switch (c) { + case ',': + case KEY_PPAGE: + // Page up key + if (m_first_visible_row > 0) { + if (m_first_visible_row > m_max_y) + m_first_visible_row -= m_max_y; + else + m_first_visible_row = 0; + m_selected_row_idx = m_first_visible_row; + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected(); + } + return eKeyHandled; + + case '.': + case KEY_NPAGE: + // Page down key + if (m_num_rows > m_max_y) { + if (m_first_visible_row + m_max_y < m_num_rows) { + m_first_visible_row += m_max_y; + m_selected_row_idx = m_first_visible_row; + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected(); + } + } + return eKeyHandled; + + case KEY_UP: + if (m_selected_row_idx > 0) { + --m_selected_row_idx; + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected(); + } + return eKeyHandled; + + case KEY_DOWN: + if (m_selected_row_idx + 1 < m_num_rows) { + ++m_selected_row_idx; + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected(); + } + return eKeyHandled; + + case KEY_RIGHT: + if (m_selected_item) { + if (!m_selected_item->IsExpanded()) + m_selected_item->Expand(); + } + return eKeyHandled; + + case KEY_LEFT: + if (m_selected_item) { + if (m_selected_item->IsExpanded()) + m_selected_item->Unexpand(); + else if (m_selected_item->GetParent()) { + m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex(); + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected(); + } + } + return eKeyHandled; + + case ' ': + // Toggle expansion state when SPACE is pressed + if (m_selected_item) { + if (m_selected_item->IsExpanded()) + m_selected_item->Unexpand(); + else + m_selected_item->Expand(); + } + return eKeyHandled; + + case 'h': + window.CreateHelpSubwindow(); + return eKeyHandled; + + default: + break; + } + return eKeyNotHandled; + } + +protected: + Debugger &m_debugger; + TreeDelegateSP m_delegate_sp; + TreeItem m_root; + TreeItem *m_selected_item = nullptr; + int m_num_rows = 0; + int m_selected_row_idx = 0; + int m_first_visible_row = 0; + int m_min_x = 0; + int m_min_y = 0; + int m_max_x = 0; + int m_max_y = 0; +}; + +// A tree delegate that just draws the text member of the tree item, it doesn't +// have any children or actions. +class TextTreeDelegate : public TreeDelegate { +public: + TextTreeDelegate() : TreeDelegate() {} + + ~TextTreeDelegate() override = default; + + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { + window.PutCStringTruncated(1, item.GetText().c_str()); + } + + void TreeDelegateGenerateChildren(TreeItem &item) override {} + + bool TreeDelegateItemSelected(TreeItem &item) override { return false; } +}; + +class FrameTreeDelegate : public TreeDelegate { +public: + FrameTreeDelegate() : TreeDelegate() { + FormatEntity::Parse( + "#${frame.index}: {${function.name}${function.pc-offset}}}", m_format); + } + + ~FrameTreeDelegate() override = default; + + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { + Thread *thread = (Thread *)item.GetUserData(); + if (thread) { + const uint64_t frame_idx = item.GetIdentifier(); + StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx); + if (frame_sp) { + StreamString strm; + const SymbolContext &sc = + frame_sp->GetSymbolContext(eSymbolContextEverything); + ExecutionContext exe_ctx(frame_sp); + if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr, + nullptr, false, false)) { + int right_pad = 1; + window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); + } + } + } + } + + void TreeDelegateGenerateChildren(TreeItem &item) override { + // No children for frames yet... + } + + bool TreeDelegateItemSelected(TreeItem &item) override { + Thread *thread = (Thread *)item.GetUserData(); + if (thread) { + thread->GetProcess()->GetThreadList().SetSelectedThreadByID( + thread->GetID()); + const uint64_t frame_idx = item.GetIdentifier(); + thread->SetSelectedFrameByIndex(frame_idx); + return true; + } + return false; + } + +protected: + FormatEntity::Entry m_format; +}; + +class ThreadTreeDelegate : public TreeDelegate { +public: + ThreadTreeDelegate(Debugger &debugger) + : TreeDelegate(), m_debugger(debugger) { + FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop " + "reason = ${thread.stop-reason}}", + m_format); + } + + ~ThreadTreeDelegate() override = default; + + ProcessSP GetProcess() { + return m_debugger.GetCommandInterpreter() + .GetExecutionContext() + .GetProcessSP(); + } + + ThreadSP GetThread(const TreeItem &item) { + ProcessSP process_sp = GetProcess(); + if (process_sp) + return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier()); + return ThreadSP(); + } + + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { + ThreadSP thread_sp = GetThread(item); + if (thread_sp) { + StreamString strm; + ExecutionContext exe_ctx(thread_sp); + if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, + nullptr, false, false)) { + int right_pad = 1; + window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); + } + } + } + + void TreeDelegateGenerateChildren(TreeItem &item) override { + ProcessSP process_sp = GetProcess(); + if (process_sp && process_sp->IsAlive()) { + StateType state = process_sp->GetState(); + if (StateIsStoppedState(state, true)) { + ThreadSP thread_sp = GetThread(item); + if (thread_sp) { + if (m_stop_id == process_sp->GetStopID() && + thread_sp->GetID() == m_tid) + return; // Children are already up to date + if (!m_frame_delegate_sp) { + // Always expand the thread item the first time we show it + m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>(); + } + + m_stop_id = process_sp->GetStopID(); + m_tid = thread_sp->GetID(); + + size_t num_frames = thread_sp->GetStackFrameCount(); + item.Resize(num_frames, *m_frame_delegate_sp, false); + for (size_t i = 0; i < num_frames; ++i) { + item[i].SetUserData(thread_sp.get()); + item[i].SetIdentifier(i); + } + } + return; + } + } + item.ClearChildren(); + } + + bool TreeDelegateItemSelected(TreeItem &item) override { + ProcessSP process_sp = GetProcess(); + if (process_sp && process_sp->IsAlive()) { + StateType state = process_sp->GetState(); + if (StateIsStoppedState(state, true)) { + ThreadSP thread_sp = GetThread(item); + if (thread_sp) { + ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList(); + std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex()); + ThreadSP selected_thread_sp = thread_list.GetSelectedThread(); + if (selected_thread_sp->GetID() != thread_sp->GetID()) { + thread_list.SetSelectedThreadByID(thread_sp->GetID()); + return true; + } + } + } + } + return false; + } + +protected: + Debugger &m_debugger; + std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp; + lldb::user_id_t m_tid = LLDB_INVALID_THREAD_ID; + uint32_t m_stop_id = UINT32_MAX; + FormatEntity::Entry m_format; +}; + +class ThreadsTreeDelegate : public TreeDelegate { +public: + ThreadsTreeDelegate(Debugger &debugger) + : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger) { + FormatEntity::Parse("process ${process.id}{, name = ${process.name}}", + m_format); + } + + ~ThreadsTreeDelegate() override = default; + + ProcessSP GetProcess() { + return m_debugger.GetCommandInterpreter() + .GetExecutionContext() + .GetProcessSP(); + } + + bool TreeDelegateShouldDraw() override { + ProcessSP process = GetProcess(); + if (!process) + return false; + + if (StateIsRunningState(process->GetState())) + return false; + + return true; + } + + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { + ProcessSP process_sp = GetProcess(); + if (process_sp && process_sp->IsAlive()) { + StreamString strm; + ExecutionContext exe_ctx(process_sp); + if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, + nullptr, false, false)) { + int right_pad = 1; + window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); + } + } + } + + void TreeDelegateGenerateChildren(TreeItem &item) override { + ProcessSP process_sp = GetProcess(); + m_update_selection = false; + if (process_sp && process_sp->IsAlive()) { + StateType state = process_sp->GetState(); + if (StateIsStoppedState(state, true)) { + const uint32_t stop_id = process_sp->GetStopID(); + if (m_stop_id == stop_id) + return; // Children are already up to date + + m_stop_id = stop_id; + m_update_selection = true; + + if (!m_thread_delegate_sp) { + // Always expand the thread item the first time we show it + // item.Expand(); + m_thread_delegate_sp = + std::make_shared<ThreadTreeDelegate>(m_debugger); + } + + ThreadList &threads = process_sp->GetThreadList(); + std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); + ThreadSP selected_thread = threads.GetSelectedThread(); + size_t num_threads = threads.GetSize(); + item.Resize(num_threads, *m_thread_delegate_sp, false); + for (size_t i = 0; i < num_threads; ++i) { + ThreadSP thread = threads.GetThreadAtIndex(i); + item[i].SetIdentifier(thread->GetID()); + item[i].SetMightHaveChildren(true); + if (selected_thread->GetID() == thread->GetID()) + item[i].Expand(); + } + return; + } + } + item.ClearChildren(); + } + + void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index, + TreeItem *&selected_item) override { + if (!m_update_selection) + return; + + ProcessSP process_sp = GetProcess(); + if (!(process_sp && process_sp->IsAlive())) + return; + + StateType state = process_sp->GetState(); + if (!StateIsStoppedState(state, true)) + return; + + ThreadList &threads = process_sp->GetThreadList(); + std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); + ThreadSP selected_thread = threads.GetSelectedThread(); + size_t num_threads = threads.GetSize(); + for (size_t i = 0; i < num_threads; ++i) { + ThreadSP thread = threads.GetThreadAtIndex(i); + if (selected_thread->GetID() == thread->GetID()) { + selected_item = + &root[i][thread->GetSelectedFrameIndex(SelectMostRelevantFrame)]; + selection_index = selected_item->GetRowIndex(); + return; + } + } + } + + bool TreeDelegateItemSelected(TreeItem &item) override { return false; } + + bool TreeDelegateExpandRootByDefault() override { return true; } + +protected: + std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp; + Debugger &m_debugger; + uint32_t m_stop_id = UINT32_MAX; + bool m_update_selection = false; + FormatEntity::Entry m_format; +}; + +class BreakpointLocationTreeDelegate : public TreeDelegate { +public: + BreakpointLocationTreeDelegate(Debugger &debugger) + : TreeDelegate(), m_debugger(debugger) {} + + ~BreakpointLocationTreeDelegate() override = default; + + Process *GetProcess() { + ExecutionContext exe_ctx( + m_debugger.GetCommandInterpreter().GetExecutionContext()); + return exe_ctx.GetProcessPtr(); + } + + BreakpointLocationSP GetBreakpointLocation(const TreeItem &item) { + Breakpoint *breakpoint = (Breakpoint *)item.GetUserData(); + return breakpoint->GetLocationAtIndex(item.GetIdentifier()); + } + + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { + BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item); + Process *process = GetProcess(); + StreamString stream; + stream.Printf("%i.%i: ", breakpoint_location->GetBreakpoint().GetID(), + breakpoint_location->GetID()); + Address address = breakpoint_location->GetAddress(); + address.Dump(&stream, process, Address::DumpStyleResolvedDescription, + Address::DumpStyleInvalid); + window.PutCStringTruncated(1, stream.GetString().str().c_str()); + } + + StringList ComputeDetailsList(BreakpointLocationSP breakpoint_location) { + StringList details; + + Address address = breakpoint_location->GetAddress(); + SymbolContext symbol_context; + address.CalculateSymbolContext(&symbol_context); + + if (symbol_context.module_sp) { + StreamString module_stream; + module_stream.PutCString("module = "); + symbol_context.module_sp->GetFileSpec().Dump( + module_stream.AsRawOstream()); + details.AppendString(module_stream.GetString()); + } + + if (symbol_context.comp_unit != nullptr) { + StreamString compile_unit_stream; + compile_unit_stream.PutCString("compile unit = "); + symbol_context.comp_unit->GetPrimaryFile().GetFilename().Dump( + &compile_unit_stream); + details.AppendString(compile_unit_stream.GetString()); + + if (symbol_context.function != nullptr) { + StreamString function_stream; + function_stream.PutCString("function = "); + function_stream.PutCString( + symbol_context.function->GetName().AsCString("<unknown>")); + details.AppendString(function_stream.GetString()); + } + + if (symbol_context.line_entry.line > 0) { + StreamString location_stream; + location_stream.PutCString("location = "); + symbol_context.line_entry.DumpStopContext(&location_stream, true); + details.AppendString(location_stream.GetString()); + } + + } else { + if (symbol_context.symbol) { + StreamString symbol_stream; + if (breakpoint_location->IsReExported()) + symbol_stream.PutCString("re-exported target = "); + else + symbol_stream.PutCString("symbol = "); + symbol_stream.PutCString( + symbol_context.symbol->GetName().AsCString("<unknown>")); + details.AppendString(symbol_stream.GetString()); + } + } + + Process *process = GetProcess(); + + StreamString address_stream; + address.Dump(&address_stream, process, Address::DumpStyleLoadAddress, + Address::DumpStyleModuleWithFileAddress); + details.AppendString(address_stream.GetString()); + + BreakpointSiteSP breakpoint_site = breakpoint_location->GetBreakpointSite(); + if (breakpoint_location->IsIndirect() && breakpoint_site) { + Address resolved_address; + resolved_address.SetLoadAddress(breakpoint_site->GetLoadAddress(), + &breakpoint_location->GetTarget()); + Symbol *resolved_symbol = resolved_address.CalculateSymbolContextSymbol(); + if (resolved_symbol) { + StreamString indirect_target_stream; + indirect_target_stream.PutCString("indirect target = "); + indirect_target_stream.PutCString( + resolved_symbol->GetName().GetCString()); + details.AppendString(indirect_target_stream.GetString()); + } + } + + bool is_resolved = breakpoint_location->IsResolved(); + StreamString resolved_stream; + resolved_stream.Printf("resolved = %s", is_resolved ? "true" : "false"); + details.AppendString(resolved_stream.GetString()); + + bool is_hardware = is_resolved && breakpoint_site->IsHardware(); + StreamString hardware_stream; + hardware_stream.Printf("hardware = %s", is_hardware ? "true" : "false"); + details.AppendString(hardware_stream.GetString()); + + StreamString hit_count_stream; + hit_count_stream.Printf("hit count = %-4u", + breakpoint_location->GetHitCount()); + details.AppendString(hit_count_stream.GetString()); + + return details; + } + + void TreeDelegateGenerateChildren(TreeItem &item) override { + BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item); + StringList details = ComputeDetailsList(breakpoint_location); + + if (!m_string_delegate_sp) + m_string_delegate_sp = std::make_shared<TextTreeDelegate>(); + + item.Resize(details.GetSize(), *m_string_delegate_sp, false); + for (size_t i = 0; i < details.GetSize(); i++) { + item[i].SetText(details.GetStringAtIndex(i)); + } + } + + bool TreeDelegateItemSelected(TreeItem &item) override { return false; } + +protected: + Debugger &m_debugger; + std::shared_ptr<TextTreeDelegate> m_string_delegate_sp; +}; + +class BreakpointTreeDelegate : public TreeDelegate { +public: + BreakpointTreeDelegate(Debugger &debugger) + : TreeDelegate(), m_debugger(debugger), + m_breakpoint_location_delegate_sp() {} + + ~BreakpointTreeDelegate() override = default; + + BreakpointSP GetBreakpoint(const TreeItem &item) { + TargetSP target = m_debugger.GetSelectedTarget(); + BreakpointList &breakpoints = target->GetBreakpointList(false); + return breakpoints.GetBreakpointAtIndex(item.GetIdentifier()); + } + + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { + BreakpointSP breakpoint = GetBreakpoint(item); + StreamString stream; + stream.Format("{0}: ", breakpoint->GetID()); + breakpoint->GetResolverDescription(&stream); + breakpoint->GetFilterDescription(&stream); + window.PutCStringTruncated(1, stream.GetString().str().c_str()); + } + + void TreeDelegateGenerateChildren(TreeItem &item) override { + BreakpointSP breakpoint = GetBreakpoint(item); + + if (!m_breakpoint_location_delegate_sp) + m_breakpoint_location_delegate_sp = + std::make_shared<BreakpointLocationTreeDelegate>(m_debugger); + + item.Resize(breakpoint->GetNumLocations(), + *m_breakpoint_location_delegate_sp, true); + for (size_t i = 0; i < breakpoint->GetNumLocations(); i++) { + item[i].SetIdentifier(i); + item[i].SetUserData(breakpoint.get()); + } + } + + bool TreeDelegateItemSelected(TreeItem &item) override { return false; } + +protected: + Debugger &m_debugger; + std::shared_ptr<BreakpointLocationTreeDelegate> + m_breakpoint_location_delegate_sp; +}; + +class BreakpointsTreeDelegate : public TreeDelegate { +public: + BreakpointsTreeDelegate(Debugger &debugger) + : TreeDelegate(), m_debugger(debugger), m_breakpoint_delegate_sp() {} + + ~BreakpointsTreeDelegate() override = default; + + bool TreeDelegateShouldDraw() override { + TargetSP target = m_debugger.GetSelectedTarget(); + if (!target) + return false; + + return true; + } + + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { + window.PutCString("Breakpoints"); + } + + void TreeDelegateGenerateChildren(TreeItem &item) override { + TargetSP target = m_debugger.GetSelectedTarget(); + + BreakpointList &breakpoints = target->GetBreakpointList(false); + std::unique_lock<std::recursive_mutex> lock; + breakpoints.GetListMutex(lock); + + if (!m_breakpoint_delegate_sp) + m_breakpoint_delegate_sp = + std::make_shared<BreakpointTreeDelegate>(m_debugger); + + item.Resize(breakpoints.GetSize(), *m_breakpoint_delegate_sp, true); + for (size_t i = 0; i < breakpoints.GetSize(); i++) { + item[i].SetIdentifier(i); + } + } + + bool TreeDelegateItemSelected(TreeItem &item) override { return false; } + + bool TreeDelegateExpandRootByDefault() override { return true; } + +protected: + Debugger &m_debugger; + std::shared_ptr<BreakpointTreeDelegate> m_breakpoint_delegate_sp; +}; + +class ValueObjectListDelegate : public WindowDelegate { +public: + ValueObjectListDelegate() : m_rows() {} + + ValueObjectListDelegate(ValueObjectList &valobj_list) : m_rows() { + SetValues(valobj_list); + } + + ~ValueObjectListDelegate() override = default; + + void SetValues(ValueObjectList &valobj_list) { + m_selected_row = nullptr; + m_selected_row_idx = 0; + m_first_visible_row = 0; + m_num_rows = 0; + m_rows.clear(); + for (auto &valobj_sp : valobj_list.GetObjects()) + m_rows.push_back(Row(valobj_sp, nullptr)); + } + + bool WindowDelegateDraw(Window &window, bool force) override { + m_num_rows = 0; + m_min_x = 2; + m_min_y = 1; + m_max_x = window.GetWidth() - 1; + m_max_y = window.GetHeight() - 1; + + window.Erase(); + window.DrawTitleBox(window.GetName()); + + const int num_visible_rows = NumVisibleRows(); + const int num_rows = CalculateTotalNumberRows(m_rows); + + // If we unexpanded while having something selected our total number of + // rows is less than the num visible rows, then make sure we show all the + // rows by setting the first visible row accordingly. + if (m_first_visible_row > 0 && num_rows < num_visible_rows) + m_first_visible_row = 0; + + // Make sure the selected row is always visible + if (m_selected_row_idx < m_first_visible_row) + m_first_visible_row = m_selected_row_idx; + else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) + m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; + + DisplayRows(window, m_rows, g_options); + + // Get the selected row + m_selected_row = GetRowForRowIndex(m_selected_row_idx); + // Keep the cursor on the selected row so the highlight and the cursor are + // always on the same line + if (m_selected_row) + window.MoveCursor(m_selected_row->x, m_selected_row->y); + + return true; // Drawing handled + } + + KeyHelp *WindowDelegateGetKeyHelp() override { + static curses::KeyHelp g_source_view_key_help[] = { + {KEY_UP, "Select previous item"}, + {KEY_DOWN, "Select next item"}, + {KEY_RIGHT, "Expand selected item"}, + {KEY_LEFT, "Unexpand selected item or select parent if not expanded"}, + {KEY_PPAGE, "Page up"}, + {KEY_NPAGE, "Page down"}, + {'A', "Format as annotated address"}, + {'b', "Format as binary"}, + {'B', "Format as hex bytes with ASCII"}, + {'c', "Format as character"}, + {'d', "Format as a signed integer"}, + {'D', "Format selected value using the default format for the type"}, + {'f', "Format as float"}, + {'h', "Show help dialog"}, + {'i', "Format as instructions"}, + {'o', "Format as octal"}, + {'p', "Format as pointer"}, + {'s', "Format as C string"}, + {'t', "Toggle showing/hiding type names"}, + {'u', "Format as an unsigned integer"}, + {'x', "Format as hex"}, + {'X', "Format as uppercase hex"}, + {' ', "Toggle item expansion"}, + {',', "Page up"}, + {'.', "Page down"}, + {'\0', nullptr}}; + return g_source_view_key_help; + } + + HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { + switch (c) { + case 'x': + case 'X': + case 'o': + case 's': + case 'u': + case 'd': + case 'D': + case 'i': + case 'A': + case 'p': + case 'c': + case 'b': + case 'B': + case 'f': + // Change the format for the currently selected item + if (m_selected_row) { + auto valobj_sp = m_selected_row->value.GetSP(); + if (valobj_sp) + valobj_sp->SetFormat(FormatForChar(c)); + } + return eKeyHandled; + + case 't': + // Toggle showing type names + g_options.show_types = !g_options.show_types; + return eKeyHandled; + + case ',': + case KEY_PPAGE: + // Page up key + if (m_first_visible_row > 0) { + if (static_cast<int>(m_first_visible_row) > m_max_y) + m_first_visible_row -= m_max_y; + else + m_first_visible_row = 0; + m_selected_row_idx = m_first_visible_row; + } + return eKeyHandled; + + case '.': + case KEY_NPAGE: + // Page down key + if (m_num_rows > static_cast<size_t>(m_max_y)) { + if (m_first_visible_row + m_max_y < m_num_rows) { + m_first_visible_row += m_max_y; + m_selected_row_idx = m_first_visible_row; + } + } + return eKeyHandled; + + case KEY_UP: + if (m_selected_row_idx > 0) + --m_selected_row_idx; + return eKeyHandled; + + case KEY_DOWN: + if (m_selected_row_idx + 1 < m_num_rows) + ++m_selected_row_idx; + return eKeyHandled; + + case KEY_RIGHT: + if (m_selected_row) { + if (!m_selected_row->expanded) + m_selected_row->Expand(); + } + return eKeyHandled; + + case KEY_LEFT: + if (m_selected_row) { + if (m_selected_row->expanded) + m_selected_row->Unexpand(); + else if (m_selected_row->parent) + m_selected_row_idx = m_selected_row->parent->row_idx; + } + return eKeyHandled; + + case ' ': + // Toggle expansion state when SPACE is pressed + if (m_selected_row) { + if (m_selected_row->expanded) + m_selected_row->Unexpand(); + else + m_selected_row->Expand(); + } + return eKeyHandled; + + case 'h': + window.CreateHelpSubwindow(); + return eKeyHandled; + + default: + break; + } + return eKeyNotHandled; + } + +protected: + std::vector<Row> m_rows; + Row *m_selected_row = nullptr; + uint32_t m_selected_row_idx = 0; + uint32_t m_first_visible_row = 0; + uint32_t m_num_rows = 0; + int m_min_x = 0; + int m_min_y = 0; + int m_max_x = 0; + int m_max_y = 0; + + static Format FormatForChar(int c) { + switch (c) { + case 'x': + return eFormatHex; + case 'X': + return eFormatHexUppercase; + case 'o': + return eFormatOctal; + case 's': + return eFormatCString; + case 'u': + return eFormatUnsigned; + case 'd': + return eFormatDecimal; + case 'D': + return eFormatDefault; + case 'i': + return eFormatInstruction; + case 'A': + return eFormatAddressInfo; + case 'p': + return eFormatPointer; + case 'c': + return eFormatChar; + case 'b': + return eFormatBinary; + case 'B': + return eFormatBytesWithASCII; + case 'f': + return eFormatFloat; + } + return eFormatDefault; + } + + bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options, + bool highlight, bool last_child) { + ValueObject *valobj = row.value.GetSP().get(); + + if (valobj == nullptr) + return false; + + const char *type_name = + options.show_types ? valobj->GetTypeName().GetCString() : nullptr; + const char *name = valobj->GetName().GetCString(); + const char *value = valobj->GetValueAsCString(); + const char *summary = valobj->GetSummaryAsCString(); + + window.MoveCursor(row.x, row.y); + + row.DrawTree(window); + + if (highlight) + window.AttributeOn(A_REVERSE); + + if (type_name && type_name[0]) + window.PrintfTruncated(1, "(%s) ", type_name); + + if (name && name[0]) + window.PutCStringTruncated(1, name); + + attr_t changd_attr = 0; + if (valobj->GetValueDidChange()) + changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD; + + if (value && value[0]) { + window.PutCStringTruncated(1, " = "); + if (changd_attr) + window.AttributeOn(changd_attr); + window.PutCStringTruncated(1, value); + if (changd_attr) + window.AttributeOff(changd_attr); + } + + if (summary && summary[0]) { + window.PutCStringTruncated(1, " "); + if (changd_attr) + window.AttributeOn(changd_attr); + window.PutCStringTruncated(1, summary); + if (changd_attr) + window.AttributeOff(changd_attr); + } + + if (highlight) + window.AttributeOff(A_REVERSE); + + return true; + } + + void DisplayRows(Window &window, std::vector<Row> &rows, + DisplayOptions &options) { + // > 0x25B7 + // \/ 0x25BD + + bool window_is_active = window.IsActive(); + for (auto &row : rows) { + const bool last_child = row.parent && &rows[rows.size() - 1] == &row; + // Save the row index in each Row structure + row.row_idx = m_num_rows; + if ((m_num_rows >= m_first_visible_row) && + ((m_num_rows - m_first_visible_row) < + static_cast<size_t>(NumVisibleRows()))) { + row.x = m_min_x; + row.y = m_num_rows - m_first_visible_row + 1; + if (DisplayRowObject(window, row, options, + window_is_active && + m_num_rows == m_selected_row_idx, + last_child)) { + ++m_num_rows; + } else { + row.x = 0; + row.y = 0; + } + } else { + row.x = 0; + row.y = 0; + ++m_num_rows; + } + + if (row.expanded) { + auto &children = row.GetChildren(); + if (!children.empty()) { + DisplayRows(window, children, options); + } + } + } + } + + int CalculateTotalNumberRows(std::vector<Row> &rows) { + int row_count = 0; + for (auto &row : rows) { + ++row_count; + if (row.expanded) + row_count += CalculateTotalNumberRows(row.GetChildren()); + } + return row_count; + } + + static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) { + for (auto &row : rows) { + if (row_index == 0) + return &row; + else { + --row_index; + if (row.expanded) { + auto &children = row.GetChildren(); + if (!children.empty()) { + Row *result = GetRowForRowIndexImpl(children, row_index); + if (result) + return result; + } + } + } + } + return nullptr; + } + + Row *GetRowForRowIndex(size_t row_index) { + return GetRowForRowIndexImpl(m_rows, row_index); + } + + int NumVisibleRows() const { return m_max_y - m_min_y; } + + static DisplayOptions g_options; +}; + +class FrameVariablesWindowDelegate : public ValueObjectListDelegate { +public: + FrameVariablesWindowDelegate(Debugger &debugger) + : ValueObjectListDelegate(), m_debugger(debugger) {} + + ~FrameVariablesWindowDelegate() override = default; + + const char *WindowDelegateGetHelpText() override { + return "Frame variable window keyboard shortcuts:"; + } + + bool WindowDelegateDraw(Window &window, bool force) override { + ExecutionContext exe_ctx( + m_debugger.GetCommandInterpreter().GetExecutionContext()); + Process *process = exe_ctx.GetProcessPtr(); + Block *frame_block = nullptr; + StackFrame *frame = nullptr; + + if (process) { + StateType state = process->GetState(); + if (StateIsStoppedState(state, true)) { + frame = exe_ctx.GetFramePtr(); + if (frame) + frame_block = frame->GetFrameBlock(); + } else if (StateIsRunningState(state)) { + return true; // Don't do any updating when we are running + } + } + + ValueObjectList local_values; + if (frame_block) { + // Only update the variables if they have changed + if (m_frame_block != frame_block) { + m_frame_block = frame_block; + + VariableList *locals = frame->GetVariableList(true, nullptr); + if (locals) { + const DynamicValueType use_dynamic = eDynamicDontRunTarget; + for (const VariableSP &local_sp : *locals) { + ValueObjectSP value_sp = + frame->GetValueObjectForFrameVariable(local_sp, use_dynamic); + if (value_sp) { + ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue(); + if (synthetic_value_sp) + local_values.Append(synthetic_value_sp); + else + local_values.Append(value_sp); + } + } + // Update the values + SetValues(local_values); + } + } + } else { + m_frame_block = nullptr; + // Update the values with an empty list if there is no frame + SetValues(local_values); + } + + return ValueObjectListDelegate::WindowDelegateDraw(window, force); + } + +protected: + Debugger &m_debugger; + Block *m_frame_block = nullptr; +}; + +class RegistersWindowDelegate : public ValueObjectListDelegate { +public: + RegistersWindowDelegate(Debugger &debugger) + : ValueObjectListDelegate(), m_debugger(debugger) {} + + ~RegistersWindowDelegate() override = default; + + const char *WindowDelegateGetHelpText() override { + return "Register window keyboard shortcuts:"; + } + + bool WindowDelegateDraw(Window &window, bool force) override { + ExecutionContext exe_ctx( + m_debugger.GetCommandInterpreter().GetExecutionContext()); + StackFrame *frame = exe_ctx.GetFramePtr(); + + ValueObjectList value_list; + if (frame) { + if (frame->GetStackID() != m_stack_id) { + m_stack_id = frame->GetStackID(); + RegisterContextSP reg_ctx(frame->GetRegisterContext()); + if (reg_ctx) { + const uint32_t num_sets = reg_ctx->GetRegisterSetCount(); + for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) { + value_list.Append( + ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx)); + } + } + SetValues(value_list); + } + } else { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive()) + return true; // Don't do any updating if we are running + else { + // Update the values with an empty list if there is no process or the + // process isn't alive anymore + SetValues(value_list); + } + } + return ValueObjectListDelegate::WindowDelegateDraw(window, force); + } + +protected: + Debugger &m_debugger; + StackID m_stack_id; +}; + +static const char *CursesKeyToCString(int ch) { + static char g_desc[32]; + if (ch >= KEY_F0 && ch < KEY_F0 + 64) { + snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0); + return g_desc; + } + switch (ch) { + case KEY_DOWN: + return "down"; + case KEY_UP: + return "up"; + case KEY_LEFT: + return "left"; + case KEY_RIGHT: + return "right"; + case KEY_HOME: + return "home"; + case KEY_BACKSPACE: + return "backspace"; + case KEY_DL: + return "delete-line"; + case KEY_IL: + return "insert-line"; + case KEY_DC: + return "delete-char"; + case KEY_IC: + return "insert-char"; + case KEY_CLEAR: + return "clear"; + case KEY_EOS: + return "clear-to-eos"; + case KEY_EOL: + return "clear-to-eol"; + case KEY_SF: + return "scroll-forward"; + case KEY_SR: + return "scroll-backward"; + case KEY_NPAGE: + return "page-down"; + case KEY_PPAGE: + return "page-up"; + case KEY_STAB: + return "set-tab"; + case KEY_CTAB: + return "clear-tab"; + case KEY_CATAB: + return "clear-all-tabs"; + case KEY_ENTER: + return "enter"; + case KEY_PRINT: + return "print"; + case KEY_LL: + return "lower-left key"; + case KEY_A1: + return "upper left of keypad"; + case KEY_A3: + return "upper right of keypad"; + case KEY_B2: + return "center of keypad"; + case KEY_C1: + return "lower left of keypad"; + case KEY_C3: + return "lower right of keypad"; + case KEY_BTAB: + return "back-tab key"; + case KEY_BEG: + return "begin key"; + case KEY_CANCEL: + return "cancel key"; + case KEY_CLOSE: + return "close key"; + case KEY_COMMAND: + return "command key"; + case KEY_COPY: + return "copy key"; + case KEY_CREATE: + return "create key"; + case KEY_END: + return "end key"; + case KEY_EXIT: + return "exit key"; + case KEY_FIND: + return "find key"; + case KEY_HELP: + return "help key"; + case KEY_MARK: + return "mark key"; + case KEY_MESSAGE: + return "message key"; + case KEY_MOVE: + return "move key"; + case KEY_NEXT: + return "next key"; + case KEY_OPEN: + return "open key"; + case KEY_OPTIONS: + return "options key"; + case KEY_PREVIOUS: + return "previous key"; + case KEY_REDO: + return "redo key"; + case KEY_REFERENCE: + return "reference key"; + case KEY_REFRESH: + return "refresh key"; + case KEY_REPLACE: + return "replace key"; + case KEY_RESTART: + return "restart key"; + case KEY_RESUME: + return "resume key"; + case KEY_SAVE: + return "save key"; + case KEY_SBEG: + return "shifted begin key"; + case KEY_SCANCEL: + return "shifted cancel key"; + case KEY_SCOMMAND: + return "shifted command key"; + case KEY_SCOPY: + return "shifted copy key"; + case KEY_SCREATE: + return "shifted create key"; + case KEY_SDC: + return "shifted delete-character key"; + case KEY_SDL: + return "shifted delete-line key"; + case KEY_SELECT: + return "select key"; + case KEY_SEND: + return "shifted end key"; + case KEY_SEOL: + return "shifted clear-to-end-of-line key"; + case KEY_SEXIT: + return "shifted exit key"; + case KEY_SFIND: + return "shifted find key"; + case KEY_SHELP: + return "shifted help key"; + case KEY_SHOME: + return "shifted home key"; + case KEY_SIC: + return "shifted insert-character key"; + case KEY_SLEFT: + return "shifted left-arrow key"; + case KEY_SMESSAGE: + return "shifted message key"; + case KEY_SMOVE: + return "shifted move key"; + case KEY_SNEXT: + return "shifted next key"; + case KEY_SOPTIONS: + return "shifted options key"; + case KEY_SPREVIOUS: + return "shifted previous key"; + case KEY_SPRINT: + return "shifted print key"; + case KEY_SREDO: + return "shifted redo key"; + case KEY_SREPLACE: + return "shifted replace key"; + case KEY_SRIGHT: + return "shifted right-arrow key"; + case KEY_SRSUME: + return "shifted resume key"; + case KEY_SSAVE: + return "shifted save key"; + case KEY_SSUSPEND: + return "shifted suspend key"; + case KEY_SUNDO: + return "shifted undo key"; + case KEY_SUSPEND: + return "suspend key"; + case KEY_UNDO: + return "undo key"; + case KEY_MOUSE: + return "Mouse event has occurred"; + case KEY_RESIZE: + return "Terminal resize event"; +#ifdef KEY_EVENT + case KEY_EVENT: + return "We were interrupted by an event"; +#endif + case KEY_RETURN: + return "return"; + case ' ': + return "space"; + case '\t': + return "tab"; + case KEY_ESCAPE: + return "escape"; + default: + if (llvm::isPrint(ch)) + snprintf(g_desc, sizeof(g_desc), "%c", ch); + else + snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch); + return g_desc; + } + return nullptr; +} + +HelpDialogDelegate::HelpDialogDelegate(const char *text, + KeyHelp *key_help_array) + : m_text() { + if (text && text[0]) { + m_text.SplitIntoLines(text); + m_text.AppendString(""); + } + if (key_help_array) { + for (KeyHelp *key = key_help_array; key->ch; ++key) { + StreamString key_description; + key_description.Printf("%10s - %s", CursesKeyToCString(key->ch), + key->description); + m_text.AppendString(key_description.GetString()); + } + } +} + +HelpDialogDelegate::~HelpDialogDelegate() = default; + +bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) { + window.Erase(); + const int window_height = window.GetHeight(); + int x = 2; + int y = 1; + const int min_y = y; + const int max_y = window_height - 1 - y; + const size_t num_visible_lines = max_y - min_y + 1; + const size_t num_lines = m_text.GetSize(); + const char *bottom_message; + if (num_lines <= num_visible_lines) + bottom_message = "Press any key to exit"; + else + bottom_message = "Use arrows to scroll, any other key to exit"; + window.DrawTitleBox(window.GetName(), bottom_message); + while (y <= max_y) { + window.MoveCursor(x, y); + window.PutCStringTruncated( + 1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y)); + ++y; + } + return true; +} + +HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window, + int key) { + bool done = false; + const size_t num_lines = m_text.GetSize(); + const size_t num_visible_lines = window.GetHeight() - 2; + + if (num_lines <= num_visible_lines) { + done = true; + // If we have all lines visible and don't need scrolling, then any key + // press will cause us to exit + } else { + switch (key) { + case KEY_UP: + if (m_first_visible_line > 0) + --m_first_visible_line; + break; + + case KEY_DOWN: + if (m_first_visible_line + num_visible_lines < num_lines) + ++m_first_visible_line; + break; + + case KEY_PPAGE: + case ',': + if (m_first_visible_line > 0) { + if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines) + m_first_visible_line -= num_visible_lines; + else + m_first_visible_line = 0; + } + break; + + case KEY_NPAGE: + case '.': + if (m_first_visible_line + num_visible_lines < num_lines) { + m_first_visible_line += num_visible_lines; + if (static_cast<size_t>(m_first_visible_line) > num_lines) + m_first_visible_line = num_lines - num_visible_lines; + } + break; + + default: + done = true; + break; + } + } + if (done) + window.GetParent()->RemoveSubWindow(&window); + return eKeyHandled; +} + +class ApplicationDelegate : public WindowDelegate, public MenuDelegate { +public: + enum { + eMenuID_LLDB = 1, + eMenuID_LLDBAbout, + eMenuID_LLDBExit, + + eMenuID_Target, + eMenuID_TargetCreate, + eMenuID_TargetDelete, + + eMenuID_Process, + eMenuID_ProcessAttach, + eMenuID_ProcessDetachResume, + eMenuID_ProcessDetachSuspended, + eMenuID_ProcessLaunch, + eMenuID_ProcessContinue, + eMenuID_ProcessHalt, + eMenuID_ProcessKill, + + eMenuID_Thread, + eMenuID_ThreadStepIn, + eMenuID_ThreadStepOver, + eMenuID_ThreadStepOut, + + eMenuID_View, + eMenuID_ViewBacktrace, + eMenuID_ViewRegisters, + eMenuID_ViewSource, + eMenuID_ViewVariables, + eMenuID_ViewBreakpoints, + + eMenuID_Help, + eMenuID_HelpGUIHelp + }; + + ApplicationDelegate(Application &app, Debugger &debugger) + : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {} + + ~ApplicationDelegate() override = default; + + bool WindowDelegateDraw(Window &window, bool force) override { + return false; // Drawing not handled, let standard window drawing happen + } + + HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { + switch (key) { + case '\t': + window.SelectNextWindowAsActive(); + return eKeyHandled; + + case KEY_SHIFT_TAB: + window.SelectPreviousWindowAsActive(); + return eKeyHandled; + + case 'h': + window.CreateHelpSubwindow(); + return eKeyHandled; + + case KEY_ESCAPE: + return eQuitApplication; + + default: + break; + } + return eKeyNotHandled; + } + + const char *WindowDelegateGetHelpText() override { + return "Welcome to the LLDB curses GUI.\n\n" + "Press the TAB key to change the selected view.\n" + "Each view has its own keyboard shortcuts, press 'h' to open a " + "dialog to display them.\n\n" + "Common key bindings for all views:"; + } + + KeyHelp *WindowDelegateGetKeyHelp() override { + static curses::KeyHelp g_source_view_key_help[] = { + {'\t', "Select next view"}, + {KEY_BTAB, "Select previous view"}, + {'h', "Show help dialog with view specific key bindings"}, + {',', "Page up"}, + {'.', "Page down"}, + {KEY_UP, "Select previous"}, + {KEY_DOWN, "Select next"}, + {KEY_LEFT, "Unexpand or select parent"}, + {KEY_RIGHT, "Expand"}, + {KEY_PPAGE, "Page up"}, + {KEY_NPAGE, "Page down"}, + {'\0', nullptr}}; + return g_source_view_key_help; + } + + MenuActionResult MenuDelegateAction(Menu &menu) override { + switch (menu.GetIdentifier()) { + case eMenuID_TargetCreate: { + WindowSP main_window_sp = m_app.GetMainWindow(); + FormDelegateSP form_delegate_sp = + FormDelegateSP(new TargetCreateFormDelegate(m_debugger)); + Rect bounds = main_window_sp->GetCenteredRect(80, 19); + WindowSP form_window_sp = main_window_sp->CreateSubWindow( + form_delegate_sp->GetName().c_str(), bounds, true); + WindowDelegateSP window_delegate_sp = + WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); + form_window_sp->SetDelegate(window_delegate_sp); + return MenuActionResult::Handled; + } + case eMenuID_ThreadStepIn: { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope()) { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && + StateIsStoppedState(process->GetState(), true)) + exe_ctx.GetThreadRef().StepIn(true); + } + } + return MenuActionResult::Handled; + + case eMenuID_ThreadStepOut: { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope()) { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && + StateIsStoppedState(process->GetState(), true)) { + Thread *thread = exe_ctx.GetThreadPtr(); + uint32_t frame_idx = + thread->GetSelectedFrameIndex(SelectMostRelevantFrame); + exe_ctx.GetThreadRef().StepOut(frame_idx); + } + } + } + return MenuActionResult::Handled; + + case eMenuID_ThreadStepOver: { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope()) { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && + StateIsStoppedState(process->GetState(), true)) + exe_ctx.GetThreadRef().StepOver(true); + } + } + return MenuActionResult::Handled; + + case eMenuID_ProcessAttach: { + WindowSP main_window_sp = m_app.GetMainWindow(); + FormDelegateSP form_delegate_sp = FormDelegateSP( + new ProcessAttachFormDelegate(m_debugger, main_window_sp)); + Rect bounds = main_window_sp->GetCenteredRect(80, 22); + WindowSP form_window_sp = main_window_sp->CreateSubWindow( + form_delegate_sp->GetName().c_str(), bounds, true); + WindowDelegateSP window_delegate_sp = + WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); + form_window_sp->SetDelegate(window_delegate_sp); + return MenuActionResult::Handled; + } + case eMenuID_ProcessLaunch: { + WindowSP main_window_sp = m_app.GetMainWindow(); + FormDelegateSP form_delegate_sp = FormDelegateSP( + new ProcessLaunchFormDelegate(m_debugger, main_window_sp)); + Rect bounds = main_window_sp->GetCenteredRect(80, 22); + WindowSP form_window_sp = main_window_sp->CreateSubWindow( + form_delegate_sp->GetName().c_str(), bounds, true); + WindowDelegateSP window_delegate_sp = + WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); + form_window_sp->SetDelegate(window_delegate_sp); + return MenuActionResult::Handled; + } + + case eMenuID_ProcessContinue: { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && + StateIsStoppedState(process->GetState(), true)) + process->Resume(); + } + } + return MenuActionResult::Handled; + + case eMenuID_ProcessKill: { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive()) + process->Destroy(false); + } + } + return MenuActionResult::Handled; + + case eMenuID_ProcessHalt: { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive()) + process->Halt(); + } + } + return MenuActionResult::Handled; + + case eMenuID_ProcessDetachResume: + case eMenuID_ProcessDetachSuspended: { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive()) + process->Detach(menu.GetIdentifier() == + eMenuID_ProcessDetachSuspended); + } + } + return MenuActionResult::Handled; + + case eMenuID_Process: { + // Populate the menu with all of the threads if the process is stopped + // when the Process menu gets selected and is about to display its + // submenu. + Menus &submenus = menu.GetSubmenus(); + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && + StateIsStoppedState(process->GetState(), true)) { + if (submenus.size() == 7) + menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); + else if (submenus.size() > 8) + submenus.erase(submenus.begin() + 8, submenus.end()); + + ThreadList &threads = process->GetThreadList(); + std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); + size_t num_threads = threads.GetSize(); + for (size_t i = 0; i < num_threads; ++i) { + ThreadSP thread_sp = threads.GetThreadAtIndex(i); + char menu_char = '\0'; + if (i < 9) + menu_char = '1' + i; + StreamString thread_menu_title; + thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID()); + const char *thread_name = thread_sp->GetName(); + if (thread_name && thread_name[0]) + thread_menu_title.Printf(" %s", thread_name); + else { + const char *queue_name = thread_sp->GetQueueName(); + if (queue_name && queue_name[0]) + thread_menu_title.Printf(" %s", queue_name); + } + menu.AddSubmenu( + MenuSP(new Menu(thread_menu_title.GetString().str().c_str(), + nullptr, menu_char, thread_sp->GetID()))); + } + } else if (submenus.size() > 7) { + // Remove the separator and any other thread submenu items that were + // previously added + submenus.erase(submenus.begin() + 7, submenus.end()); + } + // Since we are adding and removing items we need to recalculate the + // name lengths + menu.RecalculateNameLengths(); + } + return MenuActionResult::Handled; + + case eMenuID_ViewVariables: { + WindowSP main_window_sp = m_app.GetMainWindow(); + WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); + WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); + WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); + const Rect source_bounds = source_window_sp->GetBounds(); + + if (variables_window_sp) { + const Rect variables_bounds = variables_window_sp->GetBounds(); + + main_window_sp->RemoveSubWindow(variables_window_sp.get()); + + if (registers_window_sp) { + // We have a registers window, so give all the area back to the + // registers window + Rect registers_bounds = variables_bounds; + registers_bounds.size.width = source_bounds.size.width; + registers_window_sp->SetBounds(registers_bounds); + } else { + // We have no registers window showing so give the bottom area back + // to the source view + source_window_sp->Resize(source_bounds.size.width, + source_bounds.size.height + + variables_bounds.size.height); + } + } else { + Rect new_variables_rect; + if (registers_window_sp) { + // We have a registers window so split the area of the registers + // window into two columns where the left hand side will be the + // variables and the right hand side will be the registers + const Rect variables_bounds = registers_window_sp->GetBounds(); + Rect new_registers_rect; + variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect, + new_registers_rect); + registers_window_sp->SetBounds(new_registers_rect); + } else { + // No registers window, grab the bottom part of the source window + Rect new_source_rect; + source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, + new_variables_rect); + source_window_sp->SetBounds(new_source_rect); + } + WindowSP new_window_sp = main_window_sp->CreateSubWindow( + "Variables", new_variables_rect, false); + new_window_sp->SetDelegate( + WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); + } + touchwin(stdscr); + } + return MenuActionResult::Handled; + + case eMenuID_ViewRegisters: { + WindowSP main_window_sp = m_app.GetMainWindow(); + WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); + WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); + WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); + const Rect source_bounds = source_window_sp->GetBounds(); + + if (registers_window_sp) { + if (variables_window_sp) { + const Rect variables_bounds = variables_window_sp->GetBounds(); + + // We have a variables window, so give all the area back to the + // variables window + variables_window_sp->Resize(variables_bounds.size.width + + registers_window_sp->GetWidth(), + variables_bounds.size.height); + } else { + // We have no variables window showing so give the bottom area back + // to the source view + source_window_sp->Resize(source_bounds.size.width, + source_bounds.size.height + + registers_window_sp->GetHeight()); + } + main_window_sp->RemoveSubWindow(registers_window_sp.get()); + } else { + Rect new_regs_rect; + if (variables_window_sp) { + // We have a variables window, split it into two columns where the + // left hand side will be the variables and the right hand side will + // be the registers + const Rect variables_bounds = variables_window_sp->GetBounds(); + Rect new_vars_rect; + variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect, + new_regs_rect); + variables_window_sp->SetBounds(new_vars_rect); + } else { + // No variables window, grab the bottom part of the source window + Rect new_source_rect; + source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, + new_regs_rect); + source_window_sp->SetBounds(new_source_rect); + } + WindowSP new_window_sp = + main_window_sp->CreateSubWindow("Registers", new_regs_rect, false); + new_window_sp->SetDelegate( + WindowDelegateSP(new RegistersWindowDelegate(m_debugger))); + } + touchwin(stdscr); + } + return MenuActionResult::Handled; + + case eMenuID_ViewBreakpoints: { + WindowSP main_window_sp = m_app.GetMainWindow(); + WindowSP threads_window_sp = main_window_sp->FindSubWindow("Threads"); + WindowSP breakpoints_window_sp = + main_window_sp->FindSubWindow("Breakpoints"); + const Rect threads_bounds = threads_window_sp->GetBounds(); + + // If a breakpoints window already exists, remove it and give the area + // it used to occupy to the threads window. If it doesn't exist, split + // the threads window horizontally into two windows where the top window + // is the threads window and the bottom window is a newly added + // breakpoints window. + if (breakpoints_window_sp) { + threads_window_sp->Resize(threads_bounds.size.width, + threads_bounds.size.height + + breakpoints_window_sp->GetHeight()); + main_window_sp->RemoveSubWindow(breakpoints_window_sp.get()); + } else { + Rect new_threads_bounds, breakpoints_bounds; + threads_bounds.HorizontalSplitPercentage(0.70, new_threads_bounds, + breakpoints_bounds); + threads_window_sp->SetBounds(new_threads_bounds); + breakpoints_window_sp = main_window_sp->CreateSubWindow( + "Breakpoints", breakpoints_bounds, false); + TreeDelegateSP breakpoints_delegate_sp( + new BreakpointsTreeDelegate(m_debugger)); + breakpoints_window_sp->SetDelegate(WindowDelegateSP( + new TreeWindowDelegate(m_debugger, breakpoints_delegate_sp))); + } + touchwin(stdscr); + return MenuActionResult::Handled; + } + + case eMenuID_HelpGUIHelp: + m_app.GetMainWindow()->CreateHelpSubwindow(); + return MenuActionResult::Handled; + + default: + break; + } + + return MenuActionResult::NotHandled; + } + +protected: + Application &m_app; + Debugger &m_debugger; +}; + +class StatusBarWindowDelegate : public WindowDelegate { +public: + StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) { + FormatEntity::Parse("Thread: ${thread.id%tid}", m_format); + } + + ~StatusBarWindowDelegate() override = default; + + bool WindowDelegateDraw(Window &window, bool force) override { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + Process *process = exe_ctx.GetProcessPtr(); + Thread *thread = exe_ctx.GetThreadPtr(); + StackFrame *frame = exe_ctx.GetFramePtr(); + window.Erase(); + window.SetBackground(BlackOnWhite); + window.MoveCursor(0, 0); + if (process) { + const StateType state = process->GetState(); + window.Printf("Process: %5" PRIu64 " %10s", process->GetID(), + StateAsCString(state)); + + if (StateIsStoppedState(state, true)) { + StreamString strm; + if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, + nullptr, nullptr, false, false)) { + window.MoveCursor(40, 0); + window.PutCStringTruncated(1, strm.GetString().str().c_str()); + } + + window.MoveCursor(60, 0); + if (frame) + window.Printf("Frame: %3u PC = 0x%16.16" PRIx64, + frame->GetFrameIndex(), + frame->GetFrameCodeAddress().GetOpcodeLoadAddress( + exe_ctx.GetTargetPtr())); + } else if (state == eStateExited) { + const char *exit_desc = process->GetExitDescription(); + const int exit_status = process->GetExitStatus(); + if (exit_desc && exit_desc[0]) + window.Printf(" with status = %i (%s)", exit_status, exit_desc); + else + window.Printf(" with status = %i", exit_status); + } + } + return true; + } + +protected: + Debugger &m_debugger; + FormatEntity::Entry m_format; +}; + +class SourceFileWindowDelegate : public WindowDelegate { +public: + SourceFileWindowDelegate(Debugger &debugger) + : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(), + m_disassembly_sp(), m_disassembly_range(), m_title() {} + + ~SourceFileWindowDelegate() override = default; + + void Update(const SymbolContext &sc) { m_sc = sc; } + + uint32_t NumVisibleLines() const { return m_max_y - m_min_y; } + + const char *WindowDelegateGetHelpText() override { + return "Source/Disassembly window keyboard shortcuts:"; + } + + KeyHelp *WindowDelegateGetKeyHelp() override { + static curses::KeyHelp g_source_view_key_help[] = { + {KEY_RETURN, "Run to selected line with one shot breakpoint"}, + {KEY_UP, "Select previous source line"}, + {KEY_DOWN, "Select next source line"}, + {KEY_LEFT, "Scroll to the left"}, + {KEY_RIGHT, "Scroll to the right"}, + {KEY_PPAGE, "Page up"}, + {KEY_NPAGE, "Page down"}, + {'b', "Set breakpoint on selected source/disassembly line"}, + {'c', "Continue process"}, + {'D', "Detach with process suspended"}, + {'h', "Show help dialog"}, + {'n', "Step over (source line)"}, + {'N', "Step over (single instruction)"}, + {'f', "Step out (finish)"}, + {'s', "Step in (source line)"}, + {'S', "Step in (single instruction)"}, + {'u', "Frame up"}, + {'d', "Frame down"}, + {',', "Page up"}, + {'.', "Page down"}, + {'\0', nullptr}}; + return g_source_view_key_help; + } + + bool WindowDelegateDraw(Window &window, bool force) override { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + Process *process = exe_ctx.GetProcessPtr(); + Thread *thread = nullptr; + + bool update_location = false; + if (process) { + StateType state = process->GetState(); + if (StateIsStoppedState(state, true)) { + // We are stopped, so it is ok to + update_location = true; + } + } + + m_min_x = 1; + m_min_y = 2; + m_max_x = window.GetMaxX() - 1; + m_max_y = window.GetMaxY() - 1; + + const uint32_t num_visible_lines = NumVisibleLines(); + StackFrameSP frame_sp; + bool set_selected_line_to_pc = false; + + if (update_location) { + const bool process_alive = process->IsAlive(); + bool thread_changed = false; + if (process_alive) { + thread = exe_ctx.GetThreadPtr(); + if (thread) { + frame_sp = thread->GetSelectedFrame(SelectMostRelevantFrame); + auto tid = thread->GetID(); + thread_changed = tid != m_tid; + m_tid = tid; + } else { + if (m_tid != LLDB_INVALID_THREAD_ID) { + thread_changed = true; + m_tid = LLDB_INVALID_THREAD_ID; + } + } + } + const uint32_t stop_id = process ? process->GetStopID() : 0; + const bool stop_id_changed = stop_id != m_stop_id; + bool frame_changed = false; + m_stop_id = stop_id; + m_title.Clear(); + if (frame_sp) { + m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything); + if (m_sc.module_sp) { + m_title.Printf( + "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString()); + ConstString func_name = m_sc.GetFunctionName(); + if (func_name) + m_title.Printf("`%s", func_name.GetCString()); + } + const uint32_t frame_idx = frame_sp->GetFrameIndex(); + frame_changed = frame_idx != m_frame_idx; + m_frame_idx = frame_idx; + } else { + m_sc.Clear(true); + frame_changed = m_frame_idx != UINT32_MAX; + m_frame_idx = UINT32_MAX; + } + + const bool context_changed = + thread_changed || frame_changed || stop_id_changed; + + if (process_alive) { + if (m_sc.line_entry.IsValid()) { + m_pc_line = m_sc.line_entry.line; + if (m_pc_line != UINT32_MAX) + --m_pc_line; // Convert to zero based line number... + // Update the selected line if the stop ID changed... + if (context_changed) + m_selected_line = m_pc_line; + + if (m_file_sp && + m_file_sp->GetFileSpec() == m_sc.line_entry.GetFile()) { + // Same file, nothing to do, we should either have the lines or + // not (source file missing) + if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) { + if (m_selected_line >= m_first_visible_line + num_visible_lines) + m_first_visible_line = m_selected_line - 10; + } else { + if (m_selected_line > 10) + m_first_visible_line = m_selected_line - 10; + else + m_first_visible_line = 0; + } + } else { + // File changed, set selected line to the line with the PC + m_selected_line = m_pc_line; + m_file_sp = m_debugger.GetSourceManager().GetFile( + m_sc.line_entry.GetFile()); + if (m_file_sp) { + const size_t num_lines = m_file_sp->GetNumLines(); + m_line_width = 1; + for (size_t n = num_lines; n >= 10; n = n / 10) + ++m_line_width; + + if (num_lines < num_visible_lines || + m_selected_line < num_visible_lines) + m_first_visible_line = 0; + else + m_first_visible_line = m_selected_line - 10; + } + } + } else { + m_file_sp.reset(); + } + + if (!m_file_sp || m_file_sp->GetNumLines() == 0) { + // Show disassembly + bool prefer_file_cache = false; + if (m_sc.function) { + if (m_disassembly_scope != m_sc.function) { + m_disassembly_scope = m_sc.function; + m_disassembly_sp = m_sc.function->GetInstructions( + exe_ctx, nullptr, !prefer_file_cache); + if (m_disassembly_sp) { + set_selected_line_to_pc = true; + m_disassembly_range = m_sc.function->GetAddressRange(); + } else { + m_disassembly_range.Clear(); + } + } else { + set_selected_line_to_pc = context_changed; + } + } else if (m_sc.symbol) { + if (m_disassembly_scope != m_sc.symbol) { + m_disassembly_scope = m_sc.symbol; + m_disassembly_sp = m_sc.symbol->GetInstructions( + exe_ctx, nullptr, prefer_file_cache); + if (m_disassembly_sp) { + set_selected_line_to_pc = true; + m_disassembly_range.GetBaseAddress() = + m_sc.symbol->GetAddress(); + m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize()); + } else { + m_disassembly_range.Clear(); + } + } else { + set_selected_line_to_pc = context_changed; + } + } + } + } else { + m_pc_line = UINT32_MAX; + } + } + + const int window_width = window.GetWidth(); + window.Erase(); + window.DrawTitleBox("Sources"); + if (!m_title.GetString().empty()) { + window.AttributeOn(A_REVERSE); + window.MoveCursor(1, 1); + window.PutChar(' '); + window.PutCStringTruncated(1, m_title.GetString().str().c_str()); + int x = window.GetCursorX(); + if (x < window_width - 1) { + window.Printf("%*s", window_width - x - 1, ""); + } + window.AttributeOff(A_REVERSE); + } + + Target *target = exe_ctx.GetTargetPtr(); + const size_t num_source_lines = GetNumSourceLines(); + if (num_source_lines > 0) { + // Display source + BreakpointLines bp_lines; + if (target) { + BreakpointList &bp_list = target->GetBreakpointList(); + const size_t num_bps = bp_list.GetSize(); + for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { + BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); + const size_t num_bps_locs = bp_sp->GetNumLocations(); + for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { + BreakpointLocationSP bp_loc_sp = + bp_sp->GetLocationAtIndex(bp_loc_idx); + LineEntry bp_loc_line_entry; + if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( + bp_loc_line_entry)) { + if (m_file_sp->GetFileSpec() == bp_loc_line_entry.GetFile()) { + bp_lines.insert(bp_loc_line_entry.line); + } + } + } + } + } + + for (size_t i = 0; i < num_visible_lines; ++i) { + const uint32_t curr_line = m_first_visible_line + i; + if (curr_line < num_source_lines) { + const int line_y = m_min_y + i; + window.MoveCursor(1, line_y); + const bool is_pc_line = curr_line == m_pc_line; + const bool line_is_selected = m_selected_line == curr_line; + // Highlight the line as the PC line first (done by passing + // argument to OutputColoredStringTruncated()), then if the selected + // line isn't the same as the PC line, highlight it differently. + attr_t highlight_attr = 0; + attr_t bp_attr = 0; + if (line_is_selected && !is_pc_line) + highlight_attr = A_REVERSE; + + if (bp_lines.find(curr_line + 1) != bp_lines.end()) + bp_attr = COLOR_PAIR(BlackOnWhite); + + if (bp_attr) + window.AttributeOn(bp_attr); + + window.Printf(" %*u ", m_line_width, curr_line + 1); + + if (bp_attr) + window.AttributeOff(bp_attr); + + window.PutChar(ACS_VLINE); + // Mark the line with the PC with a diamond + if (is_pc_line) + window.PutChar(ACS_DIAMOND); + else + window.PutChar(' '); + + if (highlight_attr) + window.AttributeOn(highlight_attr); + + StreamString lineStream; + + std::optional<size_t> column; + if (is_pc_line && m_sc.line_entry.IsValid() && m_sc.line_entry.column) + column = m_sc.line_entry.column - 1; + m_file_sp->DisplaySourceLines(curr_line + 1, column, 0, 0, + &lineStream); + StringRef line = lineStream.GetString(); + if (line.ends_with("\n")) + line = line.drop_back(); + bool wasWritten = window.OutputColoredStringTruncated( + 1, line, m_first_visible_column, is_pc_line); + if (!wasWritten && (line_is_selected || is_pc_line)) { + // Draw an empty space to show the selected/PC line if empty, + // or draw '<' if nothing is visible because of scrolling too much + // to the right. + window.PutCStringTruncated( + 1, line.empty() && m_first_visible_column == 0 ? " " : "<"); + } + + if (is_pc_line && frame_sp && + frame_sp->GetConcreteFrameIndex() == 0) { + StopInfoSP stop_info_sp; + if (thread) + stop_info_sp = thread->GetStopInfo(); + if (stop_info_sp) { + const char *stop_description = stop_info_sp->GetDescription(); + if (stop_description && stop_description[0]) { + size_t stop_description_len = strlen(stop_description); + int desc_x = window_width - stop_description_len - 16; + if (desc_x - window.GetCursorX() > 0) + window.Printf("%*s", desc_x - window.GetCursorX(), ""); + window.MoveCursor(window_width - stop_description_len - 16, + line_y); + const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue); + window.AttributeOn(stop_reason_attr); + window.PrintfTruncated(1, " <<< Thread %u: %s ", + thread->GetIndexID(), stop_description); + window.AttributeOff(stop_reason_attr); + } + } else { + window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); + } + } + if (highlight_attr) + window.AttributeOff(highlight_attr); + } else { + break; + } + } + } else { + size_t num_disassembly_lines = GetNumDisassemblyLines(); + if (num_disassembly_lines > 0) { + // Display disassembly + BreakpointAddrs bp_file_addrs; + Target *target = exe_ctx.GetTargetPtr(); + if (target) { + BreakpointList &bp_list = target->GetBreakpointList(); + const size_t num_bps = bp_list.GetSize(); + for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { + BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); + const size_t num_bps_locs = bp_sp->GetNumLocations(); + for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; + ++bp_loc_idx) { + BreakpointLocationSP bp_loc_sp = + bp_sp->GetLocationAtIndex(bp_loc_idx); + LineEntry bp_loc_line_entry; + const lldb::addr_t file_addr = + bp_loc_sp->GetAddress().GetFileAddress(); + if (file_addr != LLDB_INVALID_ADDRESS) { + if (m_disassembly_range.ContainsFileAddress(file_addr)) + bp_file_addrs.insert(file_addr); + } + } + } + } + + const attr_t selected_highlight_attr = A_REVERSE; + const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue); + + StreamString strm; + + InstructionList &insts = m_disassembly_sp->GetInstructionList(); + Address pc_address; + + if (frame_sp) + pc_address = frame_sp->GetFrameCodeAddress(); + const uint32_t pc_idx = + pc_address.IsValid() + ? insts.GetIndexOfInstructionAtAddress(pc_address) + : UINT32_MAX; + if (set_selected_line_to_pc) { + m_selected_line = pc_idx; + } + + const uint32_t non_visible_pc_offset = (num_visible_lines / 5); + if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines) + m_first_visible_line = 0; + + if (pc_idx < num_disassembly_lines) { + if (pc_idx < static_cast<uint32_t>(m_first_visible_line) || + pc_idx >= m_first_visible_line + num_visible_lines) + m_first_visible_line = pc_idx - non_visible_pc_offset; + } + + for (size_t i = 0; i < num_visible_lines; ++i) { + const uint32_t inst_idx = m_first_visible_line + i; + Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get(); + if (!inst) + break; + + const int line_y = m_min_y + i; + window.MoveCursor(1, line_y); + const bool is_pc_line = frame_sp && inst_idx == pc_idx; + const bool line_is_selected = m_selected_line == inst_idx; + // Highlight the line as the PC line first, then if the selected + // line isn't the same as the PC line, highlight it differently + attr_t highlight_attr = 0; + attr_t bp_attr = 0; + if (is_pc_line) + highlight_attr = pc_highlight_attr; + else if (line_is_selected) + highlight_attr = selected_highlight_attr; + + if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) != + bp_file_addrs.end()) + bp_attr = COLOR_PAIR(BlackOnWhite); + + if (bp_attr) + window.AttributeOn(bp_attr); + + window.Printf(" 0x%16.16llx ", + static_cast<unsigned long long>( + inst->GetAddress().GetLoadAddress(target))); + + if (bp_attr) + window.AttributeOff(bp_attr); + + window.PutChar(ACS_VLINE); + // Mark the line with the PC with a diamond + if (is_pc_line) + window.PutChar(ACS_DIAMOND); + else + window.PutChar(' '); + + if (highlight_attr) + window.AttributeOn(highlight_attr); + + const char *mnemonic = inst->GetMnemonic(&exe_ctx); + const char *operands = inst->GetOperands(&exe_ctx); + const char *comment = inst->GetComment(&exe_ctx); + + if (mnemonic != nullptr && mnemonic[0] == '\0') + mnemonic = nullptr; + if (operands != nullptr && operands[0] == '\0') + operands = nullptr; + if (comment != nullptr && comment[0] == '\0') + comment = nullptr; + + strm.Clear(); + + if (mnemonic != nullptr && operands != nullptr && comment != nullptr) + strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment); + else if (mnemonic != nullptr && operands != nullptr) + strm.Printf("%-8s %s", mnemonic, operands); + else if (mnemonic != nullptr) + strm.Printf("%s", mnemonic); + + int right_pad = 1; + window.PutCStringTruncated( + right_pad, + strm.GetString().substr(m_first_visible_column).data()); + + if (is_pc_line && frame_sp && + frame_sp->GetConcreteFrameIndex() == 0) { + StopInfoSP stop_info_sp; + if (thread) + stop_info_sp = thread->GetStopInfo(); + if (stop_info_sp) { + const char *stop_description = stop_info_sp->GetDescription(); + if (stop_description && stop_description[0]) { + size_t stop_description_len = strlen(stop_description); + int desc_x = window_width - stop_description_len - 16; + if (desc_x - window.GetCursorX() > 0) + window.Printf("%*s", desc_x - window.GetCursorX(), ""); + window.MoveCursor(window_width - stop_description_len - 15, + line_y); + if (thread) + window.PrintfTruncated(1, "<<< Thread %u: %s ", + thread->GetIndexID(), + stop_description); + } + } else { + window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); + } + } + if (highlight_attr) + window.AttributeOff(highlight_attr); + } + } + } + return true; // Drawing handled + } + + size_t GetNumLines() { + size_t num_lines = GetNumSourceLines(); + if (num_lines == 0) + num_lines = GetNumDisassemblyLines(); + return num_lines; + } + + size_t GetNumSourceLines() const { + if (m_file_sp) + return m_file_sp->GetNumLines(); + return 0; + } + + size_t GetNumDisassemblyLines() const { + if (m_disassembly_sp) + return m_disassembly_sp->GetInstructionList().GetSize(); + return 0; + } + + HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { + const uint32_t num_visible_lines = NumVisibleLines(); + const size_t num_lines = GetNumLines(); + + switch (c) { + case ',': + case KEY_PPAGE: + // Page up key + if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines) + m_first_visible_line -= num_visible_lines; + else + m_first_visible_line = 0; + m_selected_line = m_first_visible_line; + return eKeyHandled; + + case '.': + case KEY_NPAGE: + // Page down key + { + if (m_first_visible_line + num_visible_lines < num_lines) + m_first_visible_line += num_visible_lines; + else if (num_lines < num_visible_lines) + m_first_visible_line = 0; + else + m_first_visible_line = num_lines - num_visible_lines; + m_selected_line = m_first_visible_line; + } + return eKeyHandled; + + case KEY_UP: + if (m_selected_line > 0) { + m_selected_line--; + if (static_cast<size_t>(m_first_visible_line) > m_selected_line) + m_first_visible_line = m_selected_line; + } + return eKeyHandled; + + case KEY_DOWN: + if (m_selected_line + 1 < num_lines) { + m_selected_line++; + if (m_first_visible_line + num_visible_lines < m_selected_line) + m_first_visible_line++; + } + return eKeyHandled; + + case KEY_LEFT: + if (m_first_visible_column > 0) + --m_first_visible_column; + return eKeyHandled; + + case KEY_RIGHT: + ++m_first_visible_column; + return eKeyHandled; + + case '\r': + case '\n': + case KEY_ENTER: + // Set a breakpoint and run to the line using a one shot breakpoint + if (GetNumSourceLines() > 0) { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) { + BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( + nullptr, // Don't limit the breakpoint to certain modules + m_file_sp->GetFileSpec(), // Source file + m_selected_line + + 1, // Source line number (m_selected_line is zero based) + 0, // Unspecified column. + 0, // No offset + eLazyBoolCalculate, // Check inlines using global setting + eLazyBoolCalculate, // Skip prologue using global setting, + false, // internal + false, // request_hardware + eLazyBoolCalculate); // move_to_nearest_code + // Make breakpoint one shot + bp_sp->GetOptions().SetOneShot(true); + exe_ctx.GetProcessRef().Resume(); + } + } else if (m_selected_line < GetNumDisassemblyLines()) { + const Instruction *inst = m_disassembly_sp->GetInstructionList() + .GetInstructionAtIndex(m_selected_line) + .get(); + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasTargetScope()) { + Address addr = inst->GetAddress(); + BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( + addr, // lldb_private::Address + false, // internal + false); // request_hardware + // Make breakpoint one shot + bp_sp->GetOptions().SetOneShot(true); + exe_ctx.GetProcessRef().Resume(); + } + } + return eKeyHandled; + + case 'b': // 'b' == toggle breakpoint on currently selected line + ToggleBreakpointOnSelectedLine(); + return eKeyHandled; + + case 'D': // 'D' == detach and keep stopped + { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) + exe_ctx.GetProcessRef().Detach(true); + } + return eKeyHandled; + + case 'c': + // 'c' == continue + { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) + exe_ctx.GetProcessRef().Resume(); + } + return eKeyHandled; + + case 'f': + // 'f' == step out (finish) + { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope() && + StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { + Thread *thread = exe_ctx.GetThreadPtr(); + uint32_t frame_idx = + thread->GetSelectedFrameIndex(SelectMostRelevantFrame); + exe_ctx.GetThreadRef().StepOut(frame_idx); + } + } + return eKeyHandled; + + case 'n': // 'n' == step over + case 'N': // 'N' == step over instruction + { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope() && + StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { + bool source_step = (c == 'n'); + exe_ctx.GetThreadRef().StepOver(source_step); + } + } + return eKeyHandled; + + case 's': // 's' == step into + case 'S': // 'S' == step into instruction + { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope() && + StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { + bool source_step = (c == 's'); + exe_ctx.GetThreadRef().StepIn(source_step); + } + } + return eKeyHandled; + + case 'u': // 'u' == frame up + case 'd': // 'd' == frame down + { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope()) { + Thread *thread = exe_ctx.GetThreadPtr(); + uint32_t frame_idx = + thread->GetSelectedFrameIndex(SelectMostRelevantFrame); + if (frame_idx == UINT32_MAX) + frame_idx = 0; + if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount()) + ++frame_idx; + else if (c == 'd' && frame_idx > 0) + --frame_idx; + if (thread->SetSelectedFrameByIndex(frame_idx, true)) + exe_ctx.SetFrameSP(thread->GetSelectedFrame(SelectMostRelevantFrame)); + } + } + return eKeyHandled; + + case 'h': + window.CreateHelpSubwindow(); + return eKeyHandled; + + default: + break; + } + return eKeyNotHandled; + } + + void ToggleBreakpointOnSelectedLine() { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (!exe_ctx.HasTargetScope()) + return; + if (GetNumSourceLines() > 0) { + // Source file breakpoint. + BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); + const size_t num_bps = bp_list.GetSize(); + for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { + BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); + const size_t num_bps_locs = bp_sp->GetNumLocations(); + for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { + BreakpointLocationSP bp_loc_sp = + bp_sp->GetLocationAtIndex(bp_loc_idx); + LineEntry bp_loc_line_entry; + if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( + bp_loc_line_entry)) { + if (m_file_sp->GetFileSpec() == bp_loc_line_entry.GetFile() && + m_selected_line + 1 == bp_loc_line_entry.line) { + bool removed = + exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); + assert(removed); + UNUSED_IF_ASSERT_DISABLED(removed); + return; // Existing breakpoint removed. + } + } + } + } + // No breakpoint found on the location, add it. + BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( + nullptr, // Don't limit the breakpoint to certain modules + m_file_sp->GetFileSpec(), // Source file + m_selected_line + + 1, // Source line number (m_selected_line is zero based) + 0, // No column specified. + 0, // No offset + eLazyBoolCalculate, // Check inlines using global setting + eLazyBoolCalculate, // Skip prologue using global setting, + false, // internal + false, // request_hardware + eLazyBoolCalculate); // move_to_nearest_code + } else { + // Disassembly breakpoint. + assert(GetNumDisassemblyLines() > 0); + assert(m_selected_line < GetNumDisassemblyLines()); + const Instruction *inst = m_disassembly_sp->GetInstructionList() + .GetInstructionAtIndex(m_selected_line) + .get(); + Address addr = inst->GetAddress(); + // Try to find it. + BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); + const size_t num_bps = bp_list.GetSize(); + for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { + BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); + const size_t num_bps_locs = bp_sp->GetNumLocations(); + for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { + BreakpointLocationSP bp_loc_sp = + bp_sp->GetLocationAtIndex(bp_loc_idx); + LineEntry bp_loc_line_entry; + const lldb::addr_t file_addr = + bp_loc_sp->GetAddress().GetFileAddress(); + if (file_addr == addr.GetFileAddress()) { + bool removed = + exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); + assert(removed); + UNUSED_IF_ASSERT_DISABLED(removed); + return; // Existing breakpoint removed. + } + } + } + // No breakpoint found on the address, add it. + BreakpointSP bp_sp = + exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address + false, // internal + false); // request_hardware + } + } + +protected: + typedef std::set<uint32_t> BreakpointLines; + typedef std::set<lldb::addr_t> BreakpointAddrs; + + Debugger &m_debugger; + SymbolContext m_sc; + SourceManager::FileSP m_file_sp; + SymbolContextScope *m_disassembly_scope = nullptr; + lldb::DisassemblerSP m_disassembly_sp; + AddressRange m_disassembly_range; + StreamString m_title; + lldb::user_id_t m_tid = LLDB_INVALID_THREAD_ID; + int m_line_width = 4; + uint32_t m_selected_line = 0; // The selected line + uint32_t m_pc_line = 0; // The line with the PC + uint32_t m_stop_id = 0; + uint32_t m_frame_idx = UINT32_MAX; + int m_first_visible_line = 0; + int m_first_visible_column = 0; + int m_min_x = 0; + int m_min_y = 0; + int m_max_x = 0; + int m_max_y = 0; +}; + +DisplayOptions ValueObjectListDelegate::g_options = {true}; + +IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger) + : IOHandler(debugger, IOHandler::Type::Curses) {} + +void IOHandlerCursesGUI::Activate() { + IOHandler::Activate(); + if (!m_app_ap) { + m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE()); + + // This is both a window and a menu delegate + std::shared_ptr<ApplicationDelegate> app_delegate_sp( + new ApplicationDelegate(*m_app_ap, m_debugger)); + + MenuDelegateSP app_menu_delegate_sp = + std::static_pointer_cast<MenuDelegate>(app_delegate_sp); + MenuSP lldb_menu_sp( + new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB)); + MenuSP exit_menuitem_sp( + new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit)); + exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit); + lldb_menu_sp->AddSubmenu(MenuSP(new Menu( + "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout))); + lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); + lldb_menu_sp->AddSubmenu(exit_menuitem_sp); + + MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2), + ApplicationDelegate::eMenuID_Target)); + target_menu_sp->AddSubmenu(MenuSP(new Menu( + "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate))); + target_menu_sp->AddSubmenu(MenuSP(new Menu( + "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete))); + + MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3), + ApplicationDelegate::eMenuID_Process)); + process_menu_sp->AddSubmenu(MenuSP(new Menu( + "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach))); + process_menu_sp->AddSubmenu( + MenuSP(new Menu("Detach and resume", nullptr, 'd', + ApplicationDelegate::eMenuID_ProcessDetachResume))); + process_menu_sp->AddSubmenu( + MenuSP(new Menu("Detach suspended", nullptr, 's', + ApplicationDelegate::eMenuID_ProcessDetachSuspended))); + process_menu_sp->AddSubmenu(MenuSP(new Menu( + "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch))); + process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); + process_menu_sp->AddSubmenu( + MenuSP(new Menu("Continue", nullptr, 'c', + ApplicationDelegate::eMenuID_ProcessContinue))); + process_menu_sp->AddSubmenu(MenuSP(new Menu( + "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt))); + process_menu_sp->AddSubmenu(MenuSP(new Menu( + "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill))); + + MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4), + ApplicationDelegate::eMenuID_Thread)); + thread_menu_sp->AddSubmenu(MenuSP(new Menu( + "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn))); + thread_menu_sp->AddSubmenu( + MenuSP(new Menu("Step Over", nullptr, 'v', + ApplicationDelegate::eMenuID_ThreadStepOver))); + thread_menu_sp->AddSubmenu(MenuSP(new Menu( + "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut))); + + MenuSP view_menu_sp( + new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View)); + view_menu_sp->AddSubmenu( + MenuSP(new Menu("Backtrace", nullptr, 't', + ApplicationDelegate::eMenuID_ViewBacktrace))); + view_menu_sp->AddSubmenu( + MenuSP(new Menu("Registers", nullptr, 'r', + ApplicationDelegate::eMenuID_ViewRegisters))); + view_menu_sp->AddSubmenu(MenuSP(new Menu( + "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource))); + view_menu_sp->AddSubmenu( + MenuSP(new Menu("Variables", nullptr, 'v', + ApplicationDelegate::eMenuID_ViewVariables))); + view_menu_sp->AddSubmenu( + MenuSP(new Menu("Breakpoints", nullptr, 'b', + ApplicationDelegate::eMenuID_ViewBreakpoints))); + + MenuSP help_menu_sp( + new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help)); + help_menu_sp->AddSubmenu(MenuSP(new Menu( + "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp))); + + m_app_ap->Initialize(); + WindowSP &main_window_sp = m_app_ap->GetMainWindow(); + + MenuSP menubar_sp(new Menu(Menu::Type::Bar)); + menubar_sp->AddSubmenu(lldb_menu_sp); + menubar_sp->AddSubmenu(target_menu_sp); + menubar_sp->AddSubmenu(process_menu_sp); + menubar_sp->AddSubmenu(thread_menu_sp); + menubar_sp->AddSubmenu(view_menu_sp); + menubar_sp->AddSubmenu(help_menu_sp); + menubar_sp->SetDelegate(app_menu_delegate_sp); + + Rect content_bounds = main_window_sp->GetFrame(); + Rect menubar_bounds = content_bounds.MakeMenuBar(); + Rect status_bounds = content_bounds.MakeStatusBar(); + Rect source_bounds; + Rect variables_bounds; + Rect threads_bounds; + Rect source_variables_bounds; + content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, + threads_bounds); + source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds, + variables_bounds); + + WindowSP menubar_window_sp = + main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false); + // Let the menubar get keys if the active window doesn't handle the keys + // that are typed so it can respond to menubar key presses. + menubar_window_sp->SetCanBeActive( + false); // Don't let the menubar become the active window + menubar_window_sp->SetDelegate(menubar_sp); + + WindowSP source_window_sp( + main_window_sp->CreateSubWindow("Source", source_bounds, true)); + WindowSP variables_window_sp( + main_window_sp->CreateSubWindow("Variables", variables_bounds, false)); + WindowSP threads_window_sp( + main_window_sp->CreateSubWindow("Threads", threads_bounds, false)); + WindowSP status_window_sp( + main_window_sp->CreateSubWindow("Status", status_bounds, false)); + status_window_sp->SetCanBeActive( + false); // Don't let the status bar become the active window + main_window_sp->SetDelegate( + std::static_pointer_cast<WindowDelegate>(app_delegate_sp)); + source_window_sp->SetDelegate( + WindowDelegateSP(new SourceFileWindowDelegate(m_debugger))); + variables_window_sp->SetDelegate( + WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); + TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger)); + threads_window_sp->SetDelegate(WindowDelegateSP( + new TreeWindowDelegate(m_debugger, thread_delegate_sp))); + status_window_sp->SetDelegate( + WindowDelegateSP(new StatusBarWindowDelegate(m_debugger))); + + // All colors with black background. + init_pair(1, COLOR_BLACK, COLOR_BLACK); + init_pair(2, COLOR_RED, COLOR_BLACK); + init_pair(3, COLOR_GREEN, COLOR_BLACK); + init_pair(4, COLOR_YELLOW, COLOR_BLACK); + init_pair(5, COLOR_BLUE, COLOR_BLACK); + init_pair(6, COLOR_MAGENTA, COLOR_BLACK); + init_pair(7, COLOR_CYAN, COLOR_BLACK); + init_pair(8, COLOR_WHITE, COLOR_BLACK); + // All colors with blue background. + init_pair(9, COLOR_BLACK, COLOR_BLUE); + init_pair(10, COLOR_RED, COLOR_BLUE); + init_pair(11, COLOR_GREEN, COLOR_BLUE); + init_pair(12, COLOR_YELLOW, COLOR_BLUE); + init_pair(13, COLOR_BLUE, COLOR_BLUE); + init_pair(14, COLOR_MAGENTA, COLOR_BLUE); + init_pair(15, COLOR_CYAN, COLOR_BLUE); + init_pair(16, COLOR_WHITE, COLOR_BLUE); + // These must match the order in the color indexes enum. + init_pair(17, COLOR_BLACK, COLOR_WHITE); + init_pair(18, COLOR_MAGENTA, COLOR_WHITE); + static_assert(LastColorPairIndex == 18, "Color indexes do not match."); + + define_key("\033[Z", KEY_SHIFT_TAB); + define_key("\033\015", KEY_ALT_ENTER); + } +} + +void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); } + +void IOHandlerCursesGUI::Run() { + m_app_ap->Run(m_debugger); + SetIsDone(true); +} + +IOHandlerCursesGUI::~IOHandlerCursesGUI() = default; + +void IOHandlerCursesGUI::Cancel() {} + +bool IOHandlerCursesGUI::Interrupt() { + return m_debugger.GetCommandInterpreter().IOHandlerInterrupt(*this); +} + +void IOHandlerCursesGUI::GotEOF() {} + +void IOHandlerCursesGUI::TerminalSizeChanged() { + m_app_ap->TerminalSizeChanged(); +} + +#endif // LLDB_ENABLE_CURSES |