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