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