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