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 40 #if LLDB_ENABLE_CURSES 41 #include "lldb/Breakpoint/BreakpointLocation.h" 42 #include "lldb/Core/Module.h" 43 #include "lldb/Core/ValueObject.h" 44 #include "lldb/Core/ValueObjectRegister.h" 45 #include "lldb/Symbol/Block.h" 46 #include "lldb/Symbol/Function.h" 47 #include "lldb/Symbol/Symbol.h" 48 #include "lldb/Symbol/VariableList.h" 49 #include "lldb/Target/Process.h" 50 #include "lldb/Target/RegisterContext.h" 51 #include "lldb/Target/StackFrame.h" 52 #include "lldb/Target/StopInfo.h" 53 #include "lldb/Target/Target.h" 54 #include "lldb/Target/Thread.h" 55 #include "lldb/Utility/State.h" 56 #endif 57 58 #include "llvm/ADT/StringRef.h" 59 60 #ifdef _WIN32 61 #include "lldb/Host/windows/windows.h" 62 #endif 63 64 #include <memory> 65 #include <mutex> 66 67 #include <cassert> 68 #include <cctype> 69 #include <cerrno> 70 #include <clocale> 71 #include <cstdint> 72 #include <cstdio> 73 #include <cstring> 74 #include <type_traits> 75 76 using namespace lldb; 77 using namespace lldb_private; 78 using llvm::None; 79 using llvm::Optional; 80 using llvm::StringRef; 81 82 // we may want curses to be disabled for some builds for instance, windows 83 #if LLDB_ENABLE_CURSES 84 85 #define KEY_RETURN 10 86 #define KEY_ESCAPE 27 87 88 namespace curses { 89 class Menu; 90 class MenuDelegate; 91 class Window; 92 class WindowDelegate; 93 typedef std::shared_ptr<Menu> MenuSP; 94 typedef std::shared_ptr<MenuDelegate> MenuDelegateSP; 95 typedef std::shared_ptr<Window> WindowSP; 96 typedef std::shared_ptr<WindowDelegate> WindowDelegateSP; 97 typedef std::vector<MenuSP> Menus; 98 typedef std::vector<WindowSP> Windows; 99 typedef std::vector<WindowDelegateSP> WindowDelegates; 100 101 #if 0 102 type summary add -s "x=${var.x}, y=${var.y}" curses::Point 103 type summary add -s "w=${var.width}, h=${var.height}" curses::Size 104 type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect 105 #endif 106 107 struct Point { 108 int x; 109 int y; 110 111 Point(int _x = 0, int _y = 0) : x(_x), y(_y) {} 112 113 void Clear() { 114 x = 0; 115 y = 0; 116 } 117 118 Point &operator+=(const Point &rhs) { 119 x += rhs.x; 120 y += rhs.y; 121 return *this; 122 } 123 124 void Dump() { printf("(x=%i, y=%i)\n", x, y); } 125 }; 126 127 bool operator==(const Point &lhs, const Point &rhs) { 128 return lhs.x == rhs.x && lhs.y == rhs.y; 129 } 130 131 bool operator!=(const Point &lhs, const Point &rhs) { 132 return lhs.x != rhs.x || lhs.y != rhs.y; 133 } 134 135 struct Size { 136 int width; 137 int height; 138 Size(int w = 0, int h = 0) : width(w), height(h) {} 139 140 void Clear() { 141 width = 0; 142 height = 0; 143 } 144 145 void Dump() { printf("(w=%i, h=%i)\n", width, height); } 146 }; 147 148 bool operator==(const Size &lhs, const Size &rhs) { 149 return lhs.width == rhs.width && lhs.height == rhs.height; 150 } 151 152 bool operator!=(const Size &lhs, const Size &rhs) { 153 return lhs.width != rhs.width || lhs.height != rhs.height; 154 } 155 156 struct Rect { 157 Point origin; 158 Size size; 159 160 Rect() : origin(), size() {} 161 162 Rect(const Point &p, const Size &s) : origin(p), size(s) {} 163 164 void Clear() { 165 origin.Clear(); 166 size.Clear(); 167 } 168 169 void Dump() { 170 printf("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width, 171 size.height); 172 } 173 174 void Inset(int w, int h) { 175 if (size.width > w * 2) 176 size.width -= w * 2; 177 origin.x += w; 178 179 if (size.height > h * 2) 180 size.height -= h * 2; 181 origin.y += h; 182 } 183 184 // Return a status bar rectangle which is the last line of this rectangle. 185 // This rectangle will be modified to not include the status bar area. 186 Rect MakeStatusBar() { 187 Rect status_bar; 188 if (size.height > 1) { 189 status_bar.origin.x = origin.x; 190 status_bar.origin.y = size.height; 191 status_bar.size.width = size.width; 192 status_bar.size.height = 1; 193 --size.height; 194 } 195 return status_bar; 196 } 197 198 // Return a menubar rectangle which is the first line of this rectangle. This 199 // rectangle will be modified to not include the menubar area. 200 Rect MakeMenuBar() { 201 Rect menubar; 202 if (size.height > 1) { 203 menubar.origin.x = origin.x; 204 menubar.origin.y = origin.y; 205 menubar.size.width = size.width; 206 menubar.size.height = 1; 207 ++origin.y; 208 --size.height; 209 } 210 return menubar; 211 } 212 213 void HorizontalSplitPercentage(float top_percentage, Rect &top, 214 Rect &bottom) const { 215 float top_height = top_percentage * size.height; 216 HorizontalSplit(top_height, top, bottom); 217 } 218 219 void HorizontalSplit(int top_height, Rect &top, Rect &bottom) const { 220 top = *this; 221 if (top_height < size.height) { 222 top.size.height = top_height; 223 bottom.origin.x = origin.x; 224 bottom.origin.y = origin.y + top.size.height; 225 bottom.size.width = size.width; 226 bottom.size.height = size.height - top.size.height; 227 } else { 228 bottom.Clear(); 229 } 230 } 231 232 void VerticalSplitPercentage(float left_percentage, Rect &left, 233 Rect &right) const { 234 float left_width = left_percentage * size.width; 235 VerticalSplit(left_width, left, right); 236 } 237 238 void VerticalSplit(int left_width, Rect &left, Rect &right) const { 239 left = *this; 240 if (left_width < size.width) { 241 left.size.width = left_width; 242 right.origin.x = origin.x + left.size.width; 243 right.origin.y = origin.y; 244 right.size.width = size.width - left.size.width; 245 right.size.height = size.height; 246 } else { 247 right.Clear(); 248 } 249 } 250 }; 251 252 bool operator==(const Rect &lhs, const Rect &rhs) { 253 return lhs.origin == rhs.origin && lhs.size == rhs.size; 254 } 255 256 bool operator!=(const Rect &lhs, const Rect &rhs) { 257 return lhs.origin != rhs.origin || lhs.size != rhs.size; 258 } 259 260 enum HandleCharResult { 261 eKeyNotHandled = 0, 262 eKeyHandled = 1, 263 eQuitApplication = 2 264 }; 265 266 enum class MenuActionResult { 267 Handled, 268 NotHandled, 269 Quit // Exit all menus and quit 270 }; 271 272 struct KeyHelp { 273 int ch; 274 const char *description; 275 }; 276 277 // COLOR_PAIR index names 278 enum { 279 // First 16 colors are 8 black background and 8 blue background colors, 280 // needed by OutputColoredStringTruncated(). 281 BlackOnBlack = 1, 282 RedOnBlack, 283 GreenOnBlack, 284 YellowOnBlack, 285 BlueOnBlack, 286 MagentaOnBlack, 287 CyanOnBlack, 288 WhiteOnBlack, 289 BlackOnBlue, 290 RedOnBlue, 291 GreenOnBlue, 292 YellowOnBlue, 293 BlueOnBlue, 294 MagentaOnBlue, 295 CyanOnBlue, 296 WhiteOnBlue, 297 // Other colors, as needed. 298 BlackOnWhite, 299 MagentaOnWhite, 300 LastColorPairIndex = MagentaOnWhite 301 }; 302 303 class WindowDelegate { 304 public: 305 virtual ~WindowDelegate() = default; 306 307 virtual bool WindowDelegateDraw(Window &window, bool force) { 308 return false; // Drawing not handled 309 } 310 311 virtual HandleCharResult WindowDelegateHandleChar(Window &window, int key) { 312 return eKeyNotHandled; 313 } 314 315 virtual const char *WindowDelegateGetHelpText() { return nullptr; } 316 317 virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; } 318 }; 319 320 class HelpDialogDelegate : public WindowDelegate { 321 public: 322 HelpDialogDelegate(const char *text, KeyHelp *key_help_array); 323 324 ~HelpDialogDelegate() override; 325 326 bool WindowDelegateDraw(Window &window, bool force) override; 327 328 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override; 329 330 size_t GetNumLines() const { return m_text.GetSize(); } 331 332 size_t GetMaxLineLength() const { return m_text.GetMaxStringLength(); } 333 334 protected: 335 StringList m_text; 336 int m_first_visible_line; 337 }; 338 339 class Window { 340 public: 341 Window(const char *name) 342 : m_name(name), m_window(nullptr), m_panel(nullptr), m_parent(nullptr), 343 m_subwindows(), m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX), 344 m_prev_active_window_idx(UINT32_MAX), m_delete(false), 345 m_needs_update(true), m_can_activate(true), m_is_subwin(false) {} 346 347 Window(const char *name, WINDOW *w, bool del = true) 348 : m_name(name), m_window(nullptr), m_panel(nullptr), m_parent(nullptr), 349 m_subwindows(), m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX), 350 m_prev_active_window_idx(UINT32_MAX), m_delete(del), 351 m_needs_update(true), m_can_activate(true), m_is_subwin(false) { 352 if (w) 353 Reset(w); 354 } 355 356 Window(const char *name, const Rect &bounds) 357 : m_name(name), m_window(nullptr), m_parent(nullptr), m_subwindows(), 358 m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX), 359 m_prev_active_window_idx(UINT32_MAX), m_delete(true), 360 m_needs_update(true), m_can_activate(true), m_is_subwin(false) { 361 Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y, 362 bounds.origin.y)); 363 } 364 365 virtual ~Window() { 366 RemoveSubWindows(); 367 Reset(); 368 } 369 370 void Reset(WINDOW *w = nullptr, bool del = true) { 371 if (m_window == w) 372 return; 373 374 if (m_panel) { 375 ::del_panel(m_panel); 376 m_panel = nullptr; 377 } 378 if (m_window && m_delete) { 379 ::delwin(m_window); 380 m_window = nullptr; 381 m_delete = false; 382 } 383 if (w) { 384 m_window = w; 385 m_panel = ::new_panel(m_window); 386 m_delete = del; 387 } 388 } 389 390 void AttributeOn(attr_t attr) { ::wattron(m_window, attr); } 391 void AttributeOff(attr_t attr) { ::wattroff(m_window, attr); } 392 void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) { 393 ::box(m_window, v_char, h_char); 394 } 395 void Clear() { ::wclear(m_window); } 396 void Erase() { ::werase(m_window); } 397 Rect GetBounds() const { 398 return Rect(GetParentOrigin(), GetSize()); 399 } // Get the rectangle in our parent window 400 int GetChar() { return ::wgetch(m_window); } 401 int GetCursorX() const { return getcurx(m_window); } 402 int GetCursorY() const { return getcury(m_window); } 403 Rect GetFrame() const { 404 return Rect(Point(), GetSize()); 405 } // Get our rectangle in our own coordinate system 406 Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); } 407 Size GetSize() const { return Size(GetWidth(), GetHeight()); } 408 int GetParentX() const { return getparx(m_window); } 409 int GetParentY() const { return getpary(m_window); } 410 int GetMaxX() const { return getmaxx(m_window); } 411 int GetMaxY() const { return getmaxy(m_window); } 412 int GetWidth() const { return GetMaxX(); } 413 int GetHeight() const { return GetMaxY(); } 414 void MoveCursor(int x, int y) { ::wmove(m_window, y, x); } 415 void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); } 416 void Resize(int w, int h) { ::wresize(m_window, h, w); } 417 void Resize(const Size &size) { 418 ::wresize(m_window, size.height, size.width); 419 } 420 void PutChar(int ch) { ::waddch(m_window, ch); } 421 void PutCString(const char *s, int len = -1) { ::waddnstr(m_window, s, len); } 422 void SetBackground(int color_pair_idx) { 423 ::wbkgd(m_window, COLOR_PAIR(color_pair_idx)); 424 } 425 426 void PutCStringTruncated(int right_pad, const char *s, int len = -1) { 427 int bytes_left = GetWidth() - GetCursorX(); 428 if (bytes_left > right_pad) { 429 bytes_left -= right_pad; 430 ::waddnstr(m_window, s, len < 0 ? bytes_left : std::min(bytes_left, len)); 431 } 432 } 433 434 void MoveWindow(const Point &origin) { 435 const bool moving_window = origin != GetParentOrigin(); 436 if (m_is_subwin && moving_window) { 437 // Can't move subwindows, must delete and re-create 438 Size size = GetSize(); 439 Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y, 440 origin.x), 441 true); 442 } else { 443 ::mvwin(m_window, origin.y, origin.x); 444 } 445 } 446 447 void SetBounds(const Rect &bounds) { 448 const bool moving_window = bounds.origin != GetParentOrigin(); 449 if (m_is_subwin && moving_window) { 450 // Can't move subwindows, must delete and re-create 451 Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width, 452 bounds.origin.y, bounds.origin.x), 453 true); 454 } else { 455 if (moving_window) 456 MoveWindow(bounds.origin); 457 Resize(bounds.size); 458 } 459 } 460 461 void Printf(const char *format, ...) __attribute__((format(printf, 2, 3))) { 462 va_list args; 463 va_start(args, format); 464 vw_printw(m_window, format, args); 465 va_end(args); 466 } 467 468 void PrintfTruncated(int right_pad, const char *format, ...) 469 __attribute__((format(printf, 3, 4))) { 470 va_list args; 471 va_start(args, format); 472 StreamString strm; 473 strm.PrintfVarArg(format, args); 474 va_end(args); 475 PutCStringTruncated(right_pad, strm.GetData()); 476 } 477 478 size_t LimitLengthToRestOfLine(size_t length) const { 479 return std::min<size_t>(length, std::max(0, GetWidth() - GetCursorX() - 1)); 480 } 481 482 // Curses doesn't allow direct output of color escape sequences, but that's 483 // how we get source lines from the Highligher class. Read the line and 484 // convert color escape sequences to curses color attributes. Use 485 // first_skip_count to skip leading visible characters. Returns false if all 486 // visible characters were skipped due to first_skip_count. 487 bool OutputColoredStringTruncated(int right_pad, StringRef string, 488 size_t skip_first_count, 489 bool use_blue_background) { 490 attr_t saved_attr; 491 short saved_pair; 492 bool result = false; 493 wattr_get(m_window, &saved_attr, &saved_pair, nullptr); 494 if (use_blue_background) 495 ::wattron(m_window, COLOR_PAIR(WhiteOnBlue)); 496 while (!string.empty()) { 497 size_t esc_pos = string.find('\x1b'); 498 if (esc_pos == StringRef::npos) { 499 string = string.substr(skip_first_count); 500 if (!string.empty()) { 501 PutCStringTruncated(right_pad, string.data(), string.size()); 502 result = true; 503 } 504 break; 505 } 506 if (esc_pos > 0) { 507 if (skip_first_count > 0) { 508 int skip = std::min(esc_pos, skip_first_count); 509 string = string.substr(skip); 510 skip_first_count -= skip; 511 esc_pos -= skip; 512 } 513 if (esc_pos > 0) { 514 PutCStringTruncated(right_pad, string.data(), esc_pos); 515 result = true; 516 string = string.drop_front(esc_pos); 517 } 518 } 519 bool consumed = string.consume_front("\x1b"); 520 assert(consumed); 521 UNUSED_IF_ASSERT_DISABLED(consumed); 522 // This is written to match our Highlighter classes, which seem to 523 // generate only foreground color escape sequences. If necessary, this 524 // will need to be extended. 525 if (!string.consume_front("[")) { 526 llvm::errs() << "Missing '[' in color escape sequence.\n"; 527 continue; 528 } 529 // Only 8 basic foreground colors and reset, our Highlighter doesn't use 530 // anything else. 531 int value; 532 if (!!string.consumeInteger(10, value) || // Returns false on success. 533 !(value == 0 || (value >= 30 && value <= 37))) { 534 llvm::errs() << "No valid color code in color escape sequence.\n"; 535 continue; 536 } 537 if (!string.consume_front("m")) { 538 llvm::errs() << "Missing 'm' in color escape sequence.\n"; 539 continue; 540 } 541 if (value == 0) { // Reset. 542 wattr_set(m_window, saved_attr, saved_pair, nullptr); 543 if (use_blue_background) 544 ::wattron(m_window, COLOR_PAIR(WhiteOnBlue)); 545 } else { 546 // Mapped directly to first 16 color pairs (black/blue background). 547 ::wattron(m_window, 548 COLOR_PAIR(value - 30 + 1 + (use_blue_background ? 8 : 0))); 549 } 550 } 551 wattr_set(m_window, saved_attr, saved_pair, nullptr); 552 return result; 553 } 554 555 void Touch() { 556 ::touchwin(m_window); 557 if (m_parent) 558 m_parent->Touch(); 559 } 560 561 WindowSP CreateSubWindow(const char *name, const Rect &bounds, 562 bool make_active) { 563 auto get_window = [this, &bounds]() { 564 return m_window 565 ? ::subwin(m_window, bounds.size.height, bounds.size.width, 566 bounds.origin.y, bounds.origin.x) 567 : ::newwin(bounds.size.height, bounds.size.width, 568 bounds.origin.y, bounds.origin.x); 569 }; 570 WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true); 571 subwindow_sp->m_is_subwin = subwindow_sp.operator bool(); 572 subwindow_sp->m_parent = this; 573 if (make_active) { 574 m_prev_active_window_idx = m_curr_active_window_idx; 575 m_curr_active_window_idx = m_subwindows.size(); 576 } 577 m_subwindows.push_back(subwindow_sp); 578 ::top_panel(subwindow_sp->m_panel); 579 m_needs_update = true; 580 return subwindow_sp; 581 } 582 583 bool RemoveSubWindow(Window *window) { 584 Windows::iterator pos, end = m_subwindows.end(); 585 size_t i = 0; 586 for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) { 587 if ((*pos).get() == window) { 588 if (m_prev_active_window_idx == i) 589 m_prev_active_window_idx = UINT32_MAX; 590 else if (m_prev_active_window_idx != UINT32_MAX && 591 m_prev_active_window_idx > i) 592 --m_prev_active_window_idx; 593 594 if (m_curr_active_window_idx == i) 595 m_curr_active_window_idx = UINT32_MAX; 596 else if (m_curr_active_window_idx != UINT32_MAX && 597 m_curr_active_window_idx > i) 598 --m_curr_active_window_idx; 599 window->Erase(); 600 m_subwindows.erase(pos); 601 m_needs_update = true; 602 if (m_parent) 603 m_parent->Touch(); 604 else 605 ::touchwin(stdscr); 606 return true; 607 } 608 } 609 return false; 610 } 611 612 WindowSP FindSubWindow(const char *name) { 613 Windows::iterator pos, end = m_subwindows.end(); 614 size_t i = 0; 615 for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) { 616 if ((*pos)->m_name == name) 617 return *pos; 618 } 619 return WindowSP(); 620 } 621 622 void RemoveSubWindows() { 623 m_curr_active_window_idx = UINT32_MAX; 624 m_prev_active_window_idx = UINT32_MAX; 625 for (Windows::iterator pos = m_subwindows.begin(); 626 pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) { 627 (*pos)->Erase(); 628 } 629 if (m_parent) 630 m_parent->Touch(); 631 else 632 ::touchwin(stdscr); 633 } 634 635 WINDOW *get() { return m_window; } 636 637 operator WINDOW *() { return m_window; } 638 639 // Window drawing utilities 640 void DrawTitleBox(const char *title, const char *bottom_message = nullptr) { 641 attr_t attr = 0; 642 if (IsActive()) 643 attr = A_BOLD | COLOR_PAIR(BlackOnWhite); 644 else 645 attr = 0; 646 if (attr) 647 AttributeOn(attr); 648 649 Box(); 650 MoveCursor(3, 0); 651 652 if (title && title[0]) { 653 PutChar('<'); 654 PutCString(title); 655 PutChar('>'); 656 } 657 658 if (bottom_message && bottom_message[0]) { 659 int bottom_message_length = strlen(bottom_message); 660 int x = GetWidth() - 3 - (bottom_message_length + 2); 661 662 if (x > 0) { 663 MoveCursor(x, GetHeight() - 1); 664 PutChar('['); 665 PutCString(bottom_message); 666 PutChar(']'); 667 } else { 668 MoveCursor(1, GetHeight() - 1); 669 PutChar('['); 670 PutCStringTruncated(1, bottom_message); 671 } 672 } 673 if (attr) 674 AttributeOff(attr); 675 } 676 677 virtual void Draw(bool force) { 678 if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force)) 679 return; 680 681 for (auto &subwindow_sp : m_subwindows) 682 subwindow_sp->Draw(force); 683 } 684 685 bool CreateHelpSubwindow() { 686 if (m_delegate_sp) { 687 const char *text = m_delegate_sp->WindowDelegateGetHelpText(); 688 KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp(); 689 if ((text && text[0]) || key_help) { 690 std::unique_ptr<HelpDialogDelegate> help_delegate_up( 691 new HelpDialogDelegate(text, key_help)); 692 const size_t num_lines = help_delegate_up->GetNumLines(); 693 const size_t max_length = help_delegate_up->GetMaxLineLength(); 694 Rect bounds = GetBounds(); 695 bounds.Inset(1, 1); 696 if (max_length + 4 < static_cast<size_t>(bounds.size.width)) { 697 bounds.origin.x += (bounds.size.width - max_length + 4) / 2; 698 bounds.size.width = max_length + 4; 699 } else { 700 if (bounds.size.width > 100) { 701 const int inset_w = bounds.size.width / 4; 702 bounds.origin.x += inset_w; 703 bounds.size.width -= 2 * inset_w; 704 } 705 } 706 707 if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) { 708 bounds.origin.y += (bounds.size.height - num_lines + 2) / 2; 709 bounds.size.height = num_lines + 2; 710 } else { 711 if (bounds.size.height > 100) { 712 const int inset_h = bounds.size.height / 4; 713 bounds.origin.y += inset_h; 714 bounds.size.height -= 2 * inset_h; 715 } 716 } 717 WindowSP help_window_sp; 718 Window *parent_window = GetParent(); 719 if (parent_window) 720 help_window_sp = parent_window->CreateSubWindow("Help", bounds, true); 721 else 722 help_window_sp = CreateSubWindow("Help", bounds, true); 723 help_window_sp->SetDelegate( 724 WindowDelegateSP(help_delegate_up.release())); 725 return true; 726 } 727 } 728 return false; 729 } 730 731 virtual HandleCharResult HandleChar(int key) { 732 // Always check the active window first 733 HandleCharResult result = eKeyNotHandled; 734 WindowSP active_window_sp = GetActiveWindow(); 735 if (active_window_sp) { 736 result = active_window_sp->HandleChar(key); 737 if (result != eKeyNotHandled) 738 return result; 739 } 740 741 if (m_delegate_sp) { 742 result = m_delegate_sp->WindowDelegateHandleChar(*this, key); 743 if (result != eKeyNotHandled) 744 return result; 745 } 746 747 // Then check for any windows that want any keys that weren't handled. This 748 // is typically only for a menubar. Make a copy of the subwindows in case 749 // any HandleChar() functions muck with the subwindows. If we don't do 750 // this, we can crash when iterating over the subwindows. 751 Windows subwindows(m_subwindows); 752 for (auto subwindow_sp : subwindows) { 753 if (!subwindow_sp->m_can_activate) { 754 HandleCharResult result = subwindow_sp->HandleChar(key); 755 if (result != eKeyNotHandled) 756 return result; 757 } 758 } 759 760 return eKeyNotHandled; 761 } 762 763 WindowSP GetActiveWindow() { 764 if (!m_subwindows.empty()) { 765 if (m_curr_active_window_idx >= m_subwindows.size()) { 766 if (m_prev_active_window_idx < m_subwindows.size()) { 767 m_curr_active_window_idx = m_prev_active_window_idx; 768 m_prev_active_window_idx = UINT32_MAX; 769 } else if (IsActive()) { 770 m_prev_active_window_idx = UINT32_MAX; 771 m_curr_active_window_idx = UINT32_MAX; 772 773 // Find first window that wants to be active if this window is active 774 const size_t num_subwindows = m_subwindows.size(); 775 for (size_t i = 0; i < num_subwindows; ++i) { 776 if (m_subwindows[i]->GetCanBeActive()) { 777 m_curr_active_window_idx = i; 778 break; 779 } 780 } 781 } 782 } 783 784 if (m_curr_active_window_idx < m_subwindows.size()) 785 return m_subwindows[m_curr_active_window_idx]; 786 } 787 return WindowSP(); 788 } 789 790 bool GetCanBeActive() const { return m_can_activate; } 791 792 void SetCanBeActive(bool b) { m_can_activate = b; } 793 794 void SetDelegate(const WindowDelegateSP &delegate_sp) { 795 m_delegate_sp = delegate_sp; 796 } 797 798 Window *GetParent() const { return m_parent; } 799 800 bool IsActive() const { 801 if (m_parent) 802 return m_parent->GetActiveWindow().get() == this; 803 else 804 return true; // Top level window is always active 805 } 806 807 void SelectNextWindowAsActive() { 808 // Move active focus to next window 809 const int num_subwindows = m_subwindows.size(); 810 int start_idx = 0; 811 if (m_curr_active_window_idx != UINT32_MAX) { 812 m_prev_active_window_idx = m_curr_active_window_idx; 813 start_idx = m_curr_active_window_idx + 1; 814 } 815 for (int idx = start_idx; idx < num_subwindows; ++idx) { 816 if (m_subwindows[idx]->GetCanBeActive()) { 817 m_curr_active_window_idx = idx; 818 return; 819 } 820 } 821 for (int idx = 0; idx < start_idx; ++idx) { 822 if (m_subwindows[idx]->GetCanBeActive()) { 823 m_curr_active_window_idx = idx; 824 break; 825 } 826 } 827 } 828 829 void SelectPreviousWindowAsActive() { 830 // Move active focus to previous window 831 const int num_subwindows = m_subwindows.size(); 832 int start_idx = num_subwindows - 1; 833 if (m_curr_active_window_idx != UINT32_MAX) { 834 m_prev_active_window_idx = m_curr_active_window_idx; 835 start_idx = m_curr_active_window_idx - 1; 836 } 837 for (int idx = start_idx; idx >= 0; --idx) { 838 if (m_subwindows[idx]->GetCanBeActive()) { 839 m_curr_active_window_idx = idx; 840 return; 841 } 842 } 843 for (int idx = num_subwindows - 1; idx > start_idx; --idx) { 844 if (m_subwindows[idx]->GetCanBeActive()) { 845 m_curr_active_window_idx = idx; 846 break; 847 } 848 } 849 } 850 851 const char *GetName() const { return m_name.c_str(); } 852 853 protected: 854 std::string m_name; 855 WINDOW *m_window; 856 PANEL *m_panel; 857 Window *m_parent; 858 Windows m_subwindows; 859 WindowDelegateSP m_delegate_sp; 860 uint32_t m_curr_active_window_idx; 861 uint32_t m_prev_active_window_idx; 862 bool m_delete; 863 bool m_needs_update; 864 bool m_can_activate; 865 bool m_is_subwin; 866 867 private: 868 Window(const Window &) = delete; 869 const Window &operator=(const Window &) = delete; 870 }; 871 872 class MenuDelegate { 873 public: 874 virtual ~MenuDelegate() = default; 875 876 virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0; 877 }; 878 879 class Menu : public WindowDelegate { 880 public: 881 enum class Type { Invalid, Bar, Item, Separator }; 882 883 // Menubar or separator constructor 884 Menu(Type type); 885 886 // Menuitem constructor 887 Menu(const char *name, const char *key_name, int key_value, 888 uint64_t identifier); 889 890 ~Menu() override = default; 891 892 const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; } 893 894 void SetDelegate(const MenuDelegateSP &delegate_sp) { 895 m_delegate_sp = delegate_sp; 896 } 897 898 void RecalculateNameLengths(); 899 900 void AddSubmenu(const MenuSP &menu_sp); 901 902 int DrawAndRunMenu(Window &window); 903 904 void DrawMenuTitle(Window &window, bool highlight); 905 906 bool WindowDelegateDraw(Window &window, bool force) override; 907 908 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override; 909 910 MenuActionResult ActionPrivate(Menu &menu) { 911 MenuActionResult result = MenuActionResult::NotHandled; 912 if (m_delegate_sp) { 913 result = m_delegate_sp->MenuDelegateAction(menu); 914 if (result != MenuActionResult::NotHandled) 915 return result; 916 } else if (m_parent) { 917 result = m_parent->ActionPrivate(menu); 918 if (result != MenuActionResult::NotHandled) 919 return result; 920 } 921 return m_canned_result; 922 } 923 924 MenuActionResult Action() { 925 // Call the recursive action so it can try to handle it with the menu 926 // delegate, and if not, try our parent menu 927 return ActionPrivate(*this); 928 } 929 930 void SetCannedResult(MenuActionResult result) { m_canned_result = result; } 931 932 Menus &GetSubmenus() { return m_submenus; } 933 934 const Menus &GetSubmenus() const { return m_submenus; } 935 936 int GetSelectedSubmenuIndex() const { return m_selected; } 937 938 void SetSelectedSubmenuIndex(int idx) { m_selected = idx; } 939 940 Type GetType() const { return m_type; } 941 942 int GetStartingColumn() const { return m_start_col; } 943 944 void SetStartingColumn(int col) { m_start_col = col; } 945 946 int GetKeyValue() const { return m_key_value; } 947 948 std::string &GetName() { return m_name; } 949 950 int GetDrawWidth() const { 951 return m_max_submenu_name_length + m_max_submenu_key_name_length + 8; 952 } 953 954 uint64_t GetIdentifier() const { return m_identifier; } 955 956 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } 957 958 protected: 959 std::string m_name; 960 std::string m_key_name; 961 uint64_t m_identifier; 962 Type m_type; 963 int m_key_value; 964 int m_start_col; 965 int m_max_submenu_name_length; 966 int m_max_submenu_key_name_length; 967 int m_selected; 968 Menu *m_parent; 969 Menus m_submenus; 970 WindowSP m_menu_window_sp; 971 MenuActionResult m_canned_result; 972 MenuDelegateSP m_delegate_sp; 973 }; 974 975 // Menubar or separator constructor 976 Menu::Menu(Type type) 977 : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0), 978 m_start_col(0), m_max_submenu_name_length(0), 979 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr), 980 m_submenus(), m_canned_result(MenuActionResult::NotHandled), 981 m_delegate_sp() {} 982 983 // Menuitem constructor 984 Menu::Menu(const char *name, const char *key_name, int key_value, 985 uint64_t identifier) 986 : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid), 987 m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0), 988 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr), 989 m_submenus(), m_canned_result(MenuActionResult::NotHandled), 990 m_delegate_sp() { 991 if (name && name[0]) { 992 m_name = name; 993 m_type = Type::Item; 994 if (key_name && key_name[0]) 995 m_key_name = key_name; 996 } else { 997 m_type = Type::Separator; 998 } 999 } 1000 1001 void Menu::RecalculateNameLengths() { 1002 m_max_submenu_name_length = 0; 1003 m_max_submenu_key_name_length = 0; 1004 Menus &submenus = GetSubmenus(); 1005 const size_t num_submenus = submenus.size(); 1006 for (size_t i = 0; i < num_submenus; ++i) { 1007 Menu *submenu = submenus[i].get(); 1008 if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size()) 1009 m_max_submenu_name_length = submenu->m_name.size(); 1010 if (static_cast<size_t>(m_max_submenu_key_name_length) < 1011 submenu->m_key_name.size()) 1012 m_max_submenu_key_name_length = submenu->m_key_name.size(); 1013 } 1014 } 1015 1016 void Menu::AddSubmenu(const MenuSP &menu_sp) { 1017 menu_sp->m_parent = this; 1018 if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size()) 1019 m_max_submenu_name_length = menu_sp->m_name.size(); 1020 if (static_cast<size_t>(m_max_submenu_key_name_length) < 1021 menu_sp->m_key_name.size()) 1022 m_max_submenu_key_name_length = menu_sp->m_key_name.size(); 1023 m_submenus.push_back(menu_sp); 1024 } 1025 1026 void Menu::DrawMenuTitle(Window &window, bool highlight) { 1027 if (m_type == Type::Separator) { 1028 window.MoveCursor(0, window.GetCursorY()); 1029 window.PutChar(ACS_LTEE); 1030 int width = window.GetWidth(); 1031 if (width > 2) { 1032 width -= 2; 1033 for (int i = 0; i < width; ++i) 1034 window.PutChar(ACS_HLINE); 1035 } 1036 window.PutChar(ACS_RTEE); 1037 } else { 1038 const int shortcut_key = m_key_value; 1039 bool underlined_shortcut = false; 1040 const attr_t highlight_attr = A_REVERSE; 1041 if (highlight) 1042 window.AttributeOn(highlight_attr); 1043 if (llvm::isPrint(shortcut_key)) { 1044 size_t lower_pos = m_name.find(tolower(shortcut_key)); 1045 size_t upper_pos = m_name.find(toupper(shortcut_key)); 1046 const char *name = m_name.c_str(); 1047 size_t pos = std::min<size_t>(lower_pos, upper_pos); 1048 if (pos != std::string::npos) { 1049 underlined_shortcut = true; 1050 if (pos > 0) { 1051 window.PutCString(name, pos); 1052 name += pos; 1053 } 1054 const attr_t shortcut_attr = A_UNDERLINE | A_BOLD; 1055 window.AttributeOn(shortcut_attr); 1056 window.PutChar(name[0]); 1057 window.AttributeOff(shortcut_attr); 1058 name++; 1059 if (name[0]) 1060 window.PutCString(name); 1061 } 1062 } 1063 1064 if (!underlined_shortcut) { 1065 window.PutCString(m_name.c_str()); 1066 } 1067 1068 if (highlight) 1069 window.AttributeOff(highlight_attr); 1070 1071 if (m_key_name.empty()) { 1072 if (!underlined_shortcut && llvm::isPrint(m_key_value)) { 1073 window.AttributeOn(COLOR_PAIR(MagentaOnWhite)); 1074 window.Printf(" (%c)", m_key_value); 1075 window.AttributeOff(COLOR_PAIR(MagentaOnWhite)); 1076 } 1077 } else { 1078 window.AttributeOn(COLOR_PAIR(MagentaOnWhite)); 1079 window.Printf(" (%s)", m_key_name.c_str()); 1080 window.AttributeOff(COLOR_PAIR(MagentaOnWhite)); 1081 } 1082 } 1083 } 1084 1085 bool Menu::WindowDelegateDraw(Window &window, bool force) { 1086 Menus &submenus = GetSubmenus(); 1087 const size_t num_submenus = submenus.size(); 1088 const int selected_idx = GetSelectedSubmenuIndex(); 1089 Menu::Type menu_type = GetType(); 1090 switch (menu_type) { 1091 case Menu::Type::Bar: { 1092 window.SetBackground(BlackOnWhite); 1093 window.MoveCursor(0, 0); 1094 for (size_t i = 0; i < num_submenus; ++i) { 1095 Menu *menu = submenus[i].get(); 1096 if (i > 0) 1097 window.PutChar(' '); 1098 menu->SetStartingColumn(window.GetCursorX()); 1099 window.PutCString("| "); 1100 menu->DrawMenuTitle(window, false); 1101 } 1102 window.PutCString(" |"); 1103 } break; 1104 1105 case Menu::Type::Item: { 1106 int y = 1; 1107 int x = 3; 1108 // Draw the menu 1109 int cursor_x = 0; 1110 int cursor_y = 0; 1111 window.Erase(); 1112 window.SetBackground(BlackOnWhite); 1113 window.Box(); 1114 for (size_t i = 0; i < num_submenus; ++i) { 1115 const bool is_selected = (i == static_cast<size_t>(selected_idx)); 1116 window.MoveCursor(x, y + i); 1117 if (is_selected) { 1118 // Remember where we want the cursor to be 1119 cursor_x = x - 1; 1120 cursor_y = y + i; 1121 } 1122 submenus[i]->DrawMenuTitle(window, is_selected); 1123 } 1124 window.MoveCursor(cursor_x, cursor_y); 1125 } break; 1126 1127 default: 1128 case Menu::Type::Separator: 1129 break; 1130 } 1131 return true; // Drawing handled... 1132 } 1133 1134 HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) { 1135 HandleCharResult result = eKeyNotHandled; 1136 1137 Menus &submenus = GetSubmenus(); 1138 const size_t num_submenus = submenus.size(); 1139 const int selected_idx = GetSelectedSubmenuIndex(); 1140 Menu::Type menu_type = GetType(); 1141 if (menu_type == Menu::Type::Bar) { 1142 MenuSP run_menu_sp; 1143 switch (key) { 1144 case KEY_DOWN: 1145 case KEY_UP: 1146 // Show last menu or first menu 1147 if (selected_idx < static_cast<int>(num_submenus)) 1148 run_menu_sp = submenus[selected_idx]; 1149 else if (!submenus.empty()) 1150 run_menu_sp = submenus.front(); 1151 result = eKeyHandled; 1152 break; 1153 1154 case KEY_RIGHT: 1155 ++m_selected; 1156 if (m_selected >= static_cast<int>(num_submenus)) 1157 m_selected = 0; 1158 if (m_selected < static_cast<int>(num_submenus)) 1159 run_menu_sp = submenus[m_selected]; 1160 else if (!submenus.empty()) 1161 run_menu_sp = submenus.front(); 1162 result = eKeyHandled; 1163 break; 1164 1165 case KEY_LEFT: 1166 --m_selected; 1167 if (m_selected < 0) 1168 m_selected = num_submenus - 1; 1169 if (m_selected < static_cast<int>(num_submenus)) 1170 run_menu_sp = submenus[m_selected]; 1171 else if (!submenus.empty()) 1172 run_menu_sp = submenus.front(); 1173 result = eKeyHandled; 1174 break; 1175 1176 default: 1177 for (size_t i = 0; i < num_submenus; ++i) { 1178 if (submenus[i]->GetKeyValue() == key) { 1179 SetSelectedSubmenuIndex(i); 1180 run_menu_sp = submenus[i]; 1181 result = eKeyHandled; 1182 break; 1183 } 1184 } 1185 break; 1186 } 1187 1188 if (run_menu_sp) { 1189 // Run the action on this menu in case we need to populate the menu with 1190 // dynamic content and also in case check marks, and any other menu 1191 // decorations need to be calculated 1192 if (run_menu_sp->Action() == MenuActionResult::Quit) 1193 return eQuitApplication; 1194 1195 Rect menu_bounds; 1196 menu_bounds.origin.x = run_menu_sp->GetStartingColumn(); 1197 menu_bounds.origin.y = 1; 1198 menu_bounds.size.width = run_menu_sp->GetDrawWidth(); 1199 menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2; 1200 if (m_menu_window_sp) 1201 window.GetParent()->RemoveSubWindow(m_menu_window_sp.get()); 1202 1203 m_menu_window_sp = window.GetParent()->CreateSubWindow( 1204 run_menu_sp->GetName().c_str(), menu_bounds, true); 1205 m_menu_window_sp->SetDelegate(run_menu_sp); 1206 } 1207 } else if (menu_type == Menu::Type::Item) { 1208 switch (key) { 1209 case KEY_DOWN: 1210 if (m_submenus.size() > 1) { 1211 const int start_select = m_selected; 1212 while (++m_selected != start_select) { 1213 if (static_cast<size_t>(m_selected) >= num_submenus) 1214 m_selected = 0; 1215 if (m_submenus[m_selected]->GetType() == Type::Separator) 1216 continue; 1217 else 1218 break; 1219 } 1220 return eKeyHandled; 1221 } 1222 break; 1223 1224 case KEY_UP: 1225 if (m_submenus.size() > 1) { 1226 const int start_select = m_selected; 1227 while (--m_selected != start_select) { 1228 if (m_selected < static_cast<int>(0)) 1229 m_selected = num_submenus - 1; 1230 if (m_submenus[m_selected]->GetType() == Type::Separator) 1231 continue; 1232 else 1233 break; 1234 } 1235 return eKeyHandled; 1236 } 1237 break; 1238 1239 case KEY_RETURN: 1240 if (static_cast<size_t>(selected_idx) < num_submenus) { 1241 if (submenus[selected_idx]->Action() == MenuActionResult::Quit) 1242 return eQuitApplication; 1243 window.GetParent()->RemoveSubWindow(&window); 1244 return eKeyHandled; 1245 } 1246 break; 1247 1248 case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in 1249 // case other chars are entered for escaped sequences 1250 window.GetParent()->RemoveSubWindow(&window); 1251 return eKeyHandled; 1252 1253 default: 1254 for (size_t i = 0; i < num_submenus; ++i) { 1255 Menu *menu = submenus[i].get(); 1256 if (menu->GetKeyValue() == key) { 1257 SetSelectedSubmenuIndex(i); 1258 window.GetParent()->RemoveSubWindow(&window); 1259 if (menu->Action() == MenuActionResult::Quit) 1260 return eQuitApplication; 1261 return eKeyHandled; 1262 } 1263 } 1264 break; 1265 } 1266 } else if (menu_type == Menu::Type::Separator) { 1267 } 1268 return result; 1269 } 1270 1271 class Application { 1272 public: 1273 Application(FILE *in, FILE *out) 1274 : m_window_sp(), m_screen(nullptr), m_in(in), m_out(out) {} 1275 1276 ~Application() { 1277 m_window_delegates.clear(); 1278 m_window_sp.reset(); 1279 if (m_screen) { 1280 ::delscreen(m_screen); 1281 m_screen = nullptr; 1282 } 1283 } 1284 1285 void Initialize() { 1286 ::setlocale(LC_ALL, ""); 1287 ::setlocale(LC_CTYPE, ""); 1288 m_screen = ::newterm(nullptr, m_out, m_in); 1289 ::start_color(); 1290 ::curs_set(0); 1291 ::noecho(); 1292 ::keypad(stdscr, TRUE); 1293 } 1294 1295 void Terminate() { ::endwin(); } 1296 1297 void Run(Debugger &debugger) { 1298 bool done = false; 1299 int delay_in_tenths_of_a_second = 1; 1300 1301 // Alas the threading model in curses is a bit lame so we need to resort to 1302 // polling every 0.5 seconds. We could poll for stdin ourselves and then 1303 // pass the keys down but then we need to translate all of the escape 1304 // sequences ourselves. So we resort to polling for input because we need 1305 // to receive async process events while in this loop. 1306 1307 halfdelay(delay_in_tenths_of_a_second); // Poll using some number of tenths 1308 // of seconds seconds when calling 1309 // Window::GetChar() 1310 1311 ListenerSP listener_sp( 1312 Listener::MakeListener("lldb.IOHandler.curses.Application")); 1313 ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass()); 1314 debugger.EnableForwardEvents(listener_sp); 1315 1316 m_update_screen = true; 1317 #if defined(__APPLE__) 1318 std::deque<int> escape_chars; 1319 #endif 1320 1321 while (!done) { 1322 if (m_update_screen) { 1323 m_window_sp->Draw(false); 1324 // All windows should be calling Window::DeferredRefresh() instead of 1325 // Window::Refresh() so we can do a single update and avoid any screen 1326 // blinking 1327 update_panels(); 1328 1329 // Cursor hiding isn't working on MacOSX, so hide it in the top left 1330 // corner 1331 m_window_sp->MoveCursor(0, 0); 1332 1333 doupdate(); 1334 m_update_screen = false; 1335 } 1336 1337 #if defined(__APPLE__) 1338 // Terminal.app doesn't map its function keys correctly, F1-F4 default 1339 // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if 1340 // possible 1341 int ch; 1342 if (escape_chars.empty()) 1343 ch = m_window_sp->GetChar(); 1344 else { 1345 ch = escape_chars.front(); 1346 escape_chars.pop_front(); 1347 } 1348 if (ch == KEY_ESCAPE) { 1349 int ch2 = m_window_sp->GetChar(); 1350 if (ch2 == 'O') { 1351 int ch3 = m_window_sp->GetChar(); 1352 switch (ch3) { 1353 case 'P': 1354 ch = KEY_F(1); 1355 break; 1356 case 'Q': 1357 ch = KEY_F(2); 1358 break; 1359 case 'R': 1360 ch = KEY_F(3); 1361 break; 1362 case 'S': 1363 ch = KEY_F(4); 1364 break; 1365 default: 1366 escape_chars.push_back(ch2); 1367 if (ch3 != -1) 1368 escape_chars.push_back(ch3); 1369 break; 1370 } 1371 } else if (ch2 != -1) 1372 escape_chars.push_back(ch2); 1373 } 1374 #else 1375 int ch = m_window_sp->GetChar(); 1376 1377 #endif 1378 if (ch == -1) { 1379 if (feof(m_in) || ferror(m_in)) { 1380 done = true; 1381 } else { 1382 // Just a timeout from using halfdelay(), check for events 1383 EventSP event_sp; 1384 while (listener_sp->PeekAtNextEvent()) { 1385 listener_sp->GetEvent(event_sp, std::chrono::seconds(0)); 1386 1387 if (event_sp) { 1388 Broadcaster *broadcaster = event_sp->GetBroadcaster(); 1389 if (broadcaster) { 1390 // uint32_t event_type = event_sp->GetType(); 1391 ConstString broadcaster_class( 1392 broadcaster->GetBroadcasterClass()); 1393 if (broadcaster_class == broadcaster_class_process) { 1394 m_update_screen = true; 1395 continue; // Don't get any key, just update our view 1396 } 1397 } 1398 } 1399 } 1400 } 1401 } else { 1402 HandleCharResult key_result = m_window_sp->HandleChar(ch); 1403 switch (key_result) { 1404 case eKeyHandled: 1405 m_update_screen = true; 1406 break; 1407 case eKeyNotHandled: 1408 if (ch == 12) { // Ctrl+L, force full redraw 1409 redrawwin(m_window_sp->get()); 1410 m_update_screen = true; 1411 } 1412 break; 1413 case eQuitApplication: 1414 done = true; 1415 break; 1416 } 1417 } 1418 } 1419 1420 debugger.CancelForwardEvents(listener_sp); 1421 } 1422 1423 WindowSP &GetMainWindow() { 1424 if (!m_window_sp) 1425 m_window_sp = std::make_shared<Window>("main", stdscr, false); 1426 return m_window_sp; 1427 } 1428 1429 void TerminalSizeChanged() { 1430 ::endwin(); 1431 ::refresh(); 1432 Rect content_bounds = m_window_sp->GetFrame(); 1433 m_window_sp->SetBounds(content_bounds); 1434 if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar")) 1435 menubar_window_sp->SetBounds(content_bounds.MakeMenuBar()); 1436 if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status")) 1437 status_window_sp->SetBounds(content_bounds.MakeStatusBar()); 1438 1439 WindowSP source_window_sp = m_window_sp->FindSubWindow("Source"); 1440 WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables"); 1441 WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers"); 1442 WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads"); 1443 1444 Rect threads_bounds; 1445 Rect source_variables_bounds; 1446 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, 1447 threads_bounds); 1448 if (threads_window_sp) 1449 threads_window_sp->SetBounds(threads_bounds); 1450 else 1451 source_variables_bounds = content_bounds; 1452 1453 Rect source_bounds; 1454 Rect variables_registers_bounds; 1455 source_variables_bounds.HorizontalSplitPercentage( 1456 0.70, source_bounds, variables_registers_bounds); 1457 if (variables_window_sp || registers_window_sp) { 1458 if (variables_window_sp && registers_window_sp) { 1459 Rect variables_bounds; 1460 Rect registers_bounds; 1461 variables_registers_bounds.VerticalSplitPercentage( 1462 0.50, variables_bounds, registers_bounds); 1463 variables_window_sp->SetBounds(variables_bounds); 1464 registers_window_sp->SetBounds(registers_bounds); 1465 } else if (variables_window_sp) { 1466 variables_window_sp->SetBounds(variables_registers_bounds); 1467 } else { 1468 registers_window_sp->SetBounds(variables_registers_bounds); 1469 } 1470 } else { 1471 source_bounds = source_variables_bounds; 1472 } 1473 1474 source_window_sp->SetBounds(source_bounds); 1475 1476 touchwin(stdscr); 1477 redrawwin(m_window_sp->get()); 1478 m_update_screen = true; 1479 } 1480 1481 protected: 1482 WindowSP m_window_sp; 1483 WindowDelegates m_window_delegates; 1484 SCREEN *m_screen; 1485 FILE *m_in; 1486 FILE *m_out; 1487 bool m_update_screen = false; 1488 }; 1489 1490 } // namespace curses 1491 1492 using namespace curses; 1493 1494 struct Row { 1495 ValueObjectUpdater value; 1496 Row *parent; 1497 // The process stop ID when the children were calculated. 1498 uint32_t children_stop_id = 0; 1499 int row_idx = 0; 1500 int x = 1; 1501 int y = 1; 1502 bool might_have_children; 1503 bool expanded = false; 1504 bool calculated_children = false; 1505 std::vector<Row> children; 1506 1507 Row(const ValueObjectSP &v, Row *p) 1508 : value(v), parent(p), 1509 might_have_children(v ? v->MightHaveChildren() : false) {} 1510 1511 size_t GetDepth() const { 1512 if (parent) 1513 return 1 + parent->GetDepth(); 1514 return 0; 1515 } 1516 1517 void Expand() { expanded = true; } 1518 1519 std::vector<Row> &GetChildren() { 1520 ProcessSP process_sp = value.GetProcessSP(); 1521 auto stop_id = process_sp->GetStopID(); 1522 if (process_sp && stop_id != children_stop_id) { 1523 children_stop_id = stop_id; 1524 calculated_children = false; 1525 } 1526 if (!calculated_children) { 1527 children.clear(); 1528 calculated_children = true; 1529 ValueObjectSP valobj = value.GetSP(); 1530 if (valobj) { 1531 const size_t num_children = valobj->GetNumChildren(); 1532 for (size_t i = 0; i < num_children; ++i) { 1533 children.push_back(Row(valobj->GetChildAtIndex(i, true), this)); 1534 } 1535 } 1536 } 1537 return children; 1538 } 1539 1540 void Unexpand() { 1541 expanded = false; 1542 calculated_children = false; 1543 children.clear(); 1544 } 1545 1546 void DrawTree(Window &window) { 1547 if (parent) 1548 parent->DrawTreeForChild(window, this, 0); 1549 1550 if (might_have_children) { 1551 // It we can get UTF8 characters to work we should try to use the 1552 // "symbol" UTF8 string below 1553 // const char *symbol = ""; 1554 // if (row.expanded) 1555 // symbol = "\xe2\x96\xbd "; 1556 // else 1557 // symbol = "\xe2\x96\xb7 "; 1558 // window.PutCString (symbol); 1559 1560 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v' 1561 // or '>' character... 1562 // if (expanded) 1563 // window.PutChar (ACS_DARROW); 1564 // else 1565 // window.PutChar (ACS_RARROW); 1566 // Since we can't find any good looking right arrow/down arrow symbols, 1567 // just use a diamond... 1568 window.PutChar(ACS_DIAMOND); 1569 window.PutChar(ACS_HLINE); 1570 } 1571 } 1572 1573 void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) { 1574 if (parent) 1575 parent->DrawTreeForChild(window, this, reverse_depth + 1); 1576 1577 if (&GetChildren().back() == child) { 1578 // Last child 1579 if (reverse_depth == 0) { 1580 window.PutChar(ACS_LLCORNER); 1581 window.PutChar(ACS_HLINE); 1582 } else { 1583 window.PutChar(' '); 1584 window.PutChar(' '); 1585 } 1586 } else { 1587 if (reverse_depth == 0) { 1588 window.PutChar(ACS_LTEE); 1589 window.PutChar(ACS_HLINE); 1590 } else { 1591 window.PutChar(ACS_VLINE); 1592 window.PutChar(' '); 1593 } 1594 } 1595 } 1596 }; 1597 1598 struct DisplayOptions { 1599 bool show_types; 1600 }; 1601 1602 class TreeItem; 1603 1604 class TreeDelegate { 1605 public: 1606 TreeDelegate() = default; 1607 virtual ~TreeDelegate() = default; 1608 1609 virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0; 1610 virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0; 1611 virtual bool TreeDelegateItemSelected( 1612 TreeItem &item) = 0; // Return true if we need to update views 1613 }; 1614 1615 typedef std::shared_ptr<TreeDelegate> TreeDelegateSP; 1616 1617 class TreeItem { 1618 public: 1619 TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children) 1620 : m_parent(parent), m_delegate(delegate), m_user_data(nullptr), 1621 m_identifier(0), m_row_idx(-1), m_children(), 1622 m_might_have_children(might_have_children), m_is_expanded(false) {} 1623 1624 TreeItem &operator=(const TreeItem &rhs) { 1625 if (this != &rhs) { 1626 m_parent = rhs.m_parent; 1627 m_delegate = rhs.m_delegate; 1628 m_user_data = rhs.m_user_data; 1629 m_identifier = rhs.m_identifier; 1630 m_row_idx = rhs.m_row_idx; 1631 m_children = rhs.m_children; 1632 m_might_have_children = rhs.m_might_have_children; 1633 m_is_expanded = rhs.m_is_expanded; 1634 } 1635 return *this; 1636 } 1637 1638 TreeItem(const TreeItem &) = default; 1639 1640 size_t GetDepth() const { 1641 if (m_parent) 1642 return 1 + m_parent->GetDepth(); 1643 return 0; 1644 } 1645 1646 int GetRowIndex() const { return m_row_idx; } 1647 1648 void ClearChildren() { m_children.clear(); } 1649 1650 void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); } 1651 1652 TreeItem &operator[](size_t i) { return m_children[i]; } 1653 1654 void SetRowIndex(int row_idx) { m_row_idx = row_idx; } 1655 1656 size_t GetNumChildren() { 1657 m_delegate.TreeDelegateGenerateChildren(*this); 1658 return m_children.size(); 1659 } 1660 1661 void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); } 1662 1663 void CalculateRowIndexes(int &row_idx) { 1664 SetRowIndex(row_idx); 1665 ++row_idx; 1666 1667 const bool expanded = IsExpanded(); 1668 1669 // The root item must calculate its children, or we must calculate the 1670 // number of children if the item is expanded 1671 if (m_parent == nullptr || expanded) 1672 GetNumChildren(); 1673 1674 for (auto &item : m_children) { 1675 if (expanded) 1676 item.CalculateRowIndexes(row_idx); 1677 else 1678 item.SetRowIndex(-1); 1679 } 1680 } 1681 1682 TreeItem *GetParent() { return m_parent; } 1683 1684 bool IsExpanded() const { return m_is_expanded; } 1685 1686 void Expand() { m_is_expanded = true; } 1687 1688 void Unexpand() { m_is_expanded = false; } 1689 1690 bool Draw(Window &window, const int first_visible_row, 1691 const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) { 1692 if (num_rows_left <= 0) 1693 return false; 1694 1695 if (m_row_idx >= first_visible_row) { 1696 window.MoveCursor(2, row_idx + 1); 1697 1698 if (m_parent) 1699 m_parent->DrawTreeForChild(window, this, 0); 1700 1701 if (m_might_have_children) { 1702 // It we can get UTF8 characters to work we should try to use the 1703 // "symbol" UTF8 string below 1704 // const char *symbol = ""; 1705 // if (row.expanded) 1706 // symbol = "\xe2\x96\xbd "; 1707 // else 1708 // symbol = "\xe2\x96\xb7 "; 1709 // window.PutCString (symbol); 1710 1711 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 1712 // 'v' or '>' character... 1713 // if (expanded) 1714 // window.PutChar (ACS_DARROW); 1715 // else 1716 // window.PutChar (ACS_RARROW); 1717 // Since we can't find any good looking right arrow/down arrow symbols, 1718 // just use a diamond... 1719 window.PutChar(ACS_DIAMOND); 1720 window.PutChar(ACS_HLINE); 1721 } 1722 bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) && 1723 window.IsActive(); 1724 1725 if (highlight) 1726 window.AttributeOn(A_REVERSE); 1727 1728 m_delegate.TreeDelegateDrawTreeItem(*this, window); 1729 1730 if (highlight) 1731 window.AttributeOff(A_REVERSE); 1732 ++row_idx; 1733 --num_rows_left; 1734 } 1735 1736 if (num_rows_left <= 0) 1737 return false; // We are done drawing... 1738 1739 if (IsExpanded()) { 1740 for (auto &item : m_children) { 1741 // If we displayed all the rows and item.Draw() returns false we are 1742 // done drawing and can exit this for loop 1743 if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx, 1744 num_rows_left)) 1745 break; 1746 } 1747 } 1748 return num_rows_left >= 0; // Return true if not done drawing yet 1749 } 1750 1751 void DrawTreeForChild(Window &window, TreeItem *child, 1752 uint32_t reverse_depth) { 1753 if (m_parent) 1754 m_parent->DrawTreeForChild(window, this, reverse_depth + 1); 1755 1756 if (&m_children.back() == child) { 1757 // Last child 1758 if (reverse_depth == 0) { 1759 window.PutChar(ACS_LLCORNER); 1760 window.PutChar(ACS_HLINE); 1761 } else { 1762 window.PutChar(' '); 1763 window.PutChar(' '); 1764 } 1765 } else { 1766 if (reverse_depth == 0) { 1767 window.PutChar(ACS_LTEE); 1768 window.PutChar(ACS_HLINE); 1769 } else { 1770 window.PutChar(ACS_VLINE); 1771 window.PutChar(' '); 1772 } 1773 } 1774 } 1775 1776 TreeItem *GetItemForRowIndex(uint32_t row_idx) { 1777 if (static_cast<uint32_t>(m_row_idx) == row_idx) 1778 return this; 1779 if (m_children.empty()) 1780 return nullptr; 1781 if (IsExpanded()) { 1782 for (auto &item : m_children) { 1783 TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx); 1784 if (selected_item_ptr) 1785 return selected_item_ptr; 1786 } 1787 } 1788 return nullptr; 1789 } 1790 1791 void *GetUserData() const { return m_user_data; } 1792 1793 void SetUserData(void *user_data) { m_user_data = user_data; } 1794 1795 uint64_t GetIdentifier() const { return m_identifier; } 1796 1797 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } 1798 1799 void SetMightHaveChildren(bool b) { m_might_have_children = b; } 1800 1801 protected: 1802 TreeItem *m_parent; 1803 TreeDelegate &m_delegate; 1804 void *m_user_data; 1805 uint64_t m_identifier; 1806 int m_row_idx; // Zero based visible row index, -1 if not visible or for the 1807 // root item 1808 std::vector<TreeItem> m_children; 1809 bool m_might_have_children; 1810 bool m_is_expanded; 1811 }; 1812 1813 class TreeWindowDelegate : public WindowDelegate { 1814 public: 1815 TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp) 1816 : m_debugger(debugger), m_delegate_sp(delegate_sp), 1817 m_root(nullptr, *delegate_sp, true), m_selected_item(nullptr), 1818 m_num_rows(0), m_selected_row_idx(0), m_first_visible_row(0), 1819 m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {} 1820 1821 int NumVisibleRows() const { return m_max_y - m_min_y; } 1822 1823 bool WindowDelegateDraw(Window &window, bool force) override { 1824 ExecutionContext exe_ctx( 1825 m_debugger.GetCommandInterpreter().GetExecutionContext()); 1826 Process *process = exe_ctx.GetProcessPtr(); 1827 1828 bool display_content = false; 1829 if (process) { 1830 StateType state = process->GetState(); 1831 if (StateIsStoppedState(state, true)) { 1832 // We are stopped, so it is ok to 1833 display_content = true; 1834 } else if (StateIsRunningState(state)) { 1835 return true; // Don't do any updating when we are running 1836 } 1837 } 1838 1839 m_min_x = 2; 1840 m_min_y = 1; 1841 m_max_x = window.GetWidth() - 1; 1842 m_max_y = window.GetHeight() - 1; 1843 1844 window.Erase(); 1845 window.DrawTitleBox(window.GetName()); 1846 1847 if (display_content) { 1848 const int num_visible_rows = NumVisibleRows(); 1849 m_num_rows = 0; 1850 m_root.CalculateRowIndexes(m_num_rows); 1851 1852 // If we unexpanded while having something selected our total number of 1853 // rows is less than the num visible rows, then make sure we show all the 1854 // rows by setting the first visible row accordingly. 1855 if (m_first_visible_row > 0 && m_num_rows < num_visible_rows) 1856 m_first_visible_row = 0; 1857 1858 // Make sure the selected row is always visible 1859 if (m_selected_row_idx < m_first_visible_row) 1860 m_first_visible_row = m_selected_row_idx; 1861 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) 1862 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; 1863 1864 int row_idx = 0; 1865 int num_rows_left = num_visible_rows; 1866 m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx, 1867 num_rows_left); 1868 // Get the selected row 1869 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 1870 } else { 1871 m_selected_item = nullptr; 1872 } 1873 1874 return true; // Drawing handled 1875 } 1876 1877 const char *WindowDelegateGetHelpText() override { 1878 return "Thread window keyboard shortcuts:"; 1879 } 1880 1881 KeyHelp *WindowDelegateGetKeyHelp() override { 1882 static curses::KeyHelp g_source_view_key_help[] = { 1883 {KEY_UP, "Select previous item"}, 1884 {KEY_DOWN, "Select next item"}, 1885 {KEY_RIGHT, "Expand the selected item"}, 1886 {KEY_LEFT, 1887 "Unexpand the selected item or select parent if not expanded"}, 1888 {KEY_PPAGE, "Page up"}, 1889 {KEY_NPAGE, "Page down"}, 1890 {'h', "Show help dialog"}, 1891 {' ', "Toggle item expansion"}, 1892 {',', "Page up"}, 1893 {'.', "Page down"}, 1894 {'\0', nullptr}}; 1895 return g_source_view_key_help; 1896 } 1897 1898 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 1899 switch (c) { 1900 case ',': 1901 case KEY_PPAGE: 1902 // Page up key 1903 if (m_first_visible_row > 0) { 1904 if (m_first_visible_row > m_max_y) 1905 m_first_visible_row -= m_max_y; 1906 else 1907 m_first_visible_row = 0; 1908 m_selected_row_idx = m_first_visible_row; 1909 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 1910 if (m_selected_item) 1911 m_selected_item->ItemWasSelected(); 1912 } 1913 return eKeyHandled; 1914 1915 case '.': 1916 case KEY_NPAGE: 1917 // Page down key 1918 if (m_num_rows > m_max_y) { 1919 if (m_first_visible_row + m_max_y < m_num_rows) { 1920 m_first_visible_row += m_max_y; 1921 m_selected_row_idx = m_first_visible_row; 1922 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 1923 if (m_selected_item) 1924 m_selected_item->ItemWasSelected(); 1925 } 1926 } 1927 return eKeyHandled; 1928 1929 case KEY_UP: 1930 if (m_selected_row_idx > 0) { 1931 --m_selected_row_idx; 1932 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 1933 if (m_selected_item) 1934 m_selected_item->ItemWasSelected(); 1935 } 1936 return eKeyHandled; 1937 1938 case KEY_DOWN: 1939 if (m_selected_row_idx + 1 < m_num_rows) { 1940 ++m_selected_row_idx; 1941 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 1942 if (m_selected_item) 1943 m_selected_item->ItemWasSelected(); 1944 } 1945 return eKeyHandled; 1946 1947 case KEY_RIGHT: 1948 if (m_selected_item) { 1949 if (!m_selected_item->IsExpanded()) 1950 m_selected_item->Expand(); 1951 } 1952 return eKeyHandled; 1953 1954 case KEY_LEFT: 1955 if (m_selected_item) { 1956 if (m_selected_item->IsExpanded()) 1957 m_selected_item->Unexpand(); 1958 else if (m_selected_item->GetParent()) { 1959 m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex(); 1960 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 1961 if (m_selected_item) 1962 m_selected_item->ItemWasSelected(); 1963 } 1964 } 1965 return eKeyHandled; 1966 1967 case ' ': 1968 // Toggle expansion state when SPACE is pressed 1969 if (m_selected_item) { 1970 if (m_selected_item->IsExpanded()) 1971 m_selected_item->Unexpand(); 1972 else 1973 m_selected_item->Expand(); 1974 } 1975 return eKeyHandled; 1976 1977 case 'h': 1978 window.CreateHelpSubwindow(); 1979 return eKeyHandled; 1980 1981 default: 1982 break; 1983 } 1984 return eKeyNotHandled; 1985 } 1986 1987 protected: 1988 Debugger &m_debugger; 1989 TreeDelegateSP m_delegate_sp; 1990 TreeItem m_root; 1991 TreeItem *m_selected_item; 1992 int m_num_rows; 1993 int m_selected_row_idx; 1994 int m_first_visible_row; 1995 int m_min_x; 1996 int m_min_y; 1997 int m_max_x; 1998 int m_max_y; 1999 }; 2000 2001 class FrameTreeDelegate : public TreeDelegate { 2002 public: 2003 FrameTreeDelegate() : TreeDelegate() { 2004 FormatEntity::Parse( 2005 "frame #${frame.index}: {${function.name}${function.pc-offset}}}", 2006 m_format); 2007 } 2008 2009 ~FrameTreeDelegate() override = default; 2010 2011 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 2012 Thread *thread = (Thread *)item.GetUserData(); 2013 if (thread) { 2014 const uint64_t frame_idx = item.GetIdentifier(); 2015 StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx); 2016 if (frame_sp) { 2017 StreamString strm; 2018 const SymbolContext &sc = 2019 frame_sp->GetSymbolContext(eSymbolContextEverything); 2020 ExecutionContext exe_ctx(frame_sp); 2021 if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr, 2022 nullptr, false, false)) { 2023 int right_pad = 1; 2024 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 2025 } 2026 } 2027 } 2028 } 2029 2030 void TreeDelegateGenerateChildren(TreeItem &item) override { 2031 // No children for frames yet... 2032 } 2033 2034 bool TreeDelegateItemSelected(TreeItem &item) override { 2035 Thread *thread = (Thread *)item.GetUserData(); 2036 if (thread) { 2037 thread->GetProcess()->GetThreadList().SetSelectedThreadByID( 2038 thread->GetID()); 2039 const uint64_t frame_idx = item.GetIdentifier(); 2040 thread->SetSelectedFrameByIndex(frame_idx); 2041 return true; 2042 } 2043 return false; 2044 } 2045 2046 protected: 2047 FormatEntity::Entry m_format; 2048 }; 2049 2050 class ThreadTreeDelegate : public TreeDelegate { 2051 public: 2052 ThreadTreeDelegate(Debugger &debugger) 2053 : TreeDelegate(), m_debugger(debugger), m_tid(LLDB_INVALID_THREAD_ID), 2054 m_stop_id(UINT32_MAX) { 2055 FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop " 2056 "reason = ${thread.stop-reason}}", 2057 m_format); 2058 } 2059 2060 ~ThreadTreeDelegate() override = default; 2061 2062 ProcessSP GetProcess() { 2063 return m_debugger.GetCommandInterpreter() 2064 .GetExecutionContext() 2065 .GetProcessSP(); 2066 } 2067 2068 ThreadSP GetThread(const TreeItem &item) { 2069 ProcessSP process_sp = GetProcess(); 2070 if (process_sp) 2071 return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier()); 2072 return ThreadSP(); 2073 } 2074 2075 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 2076 ThreadSP thread_sp = GetThread(item); 2077 if (thread_sp) { 2078 StreamString strm; 2079 ExecutionContext exe_ctx(thread_sp); 2080 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, 2081 nullptr, false, false)) { 2082 int right_pad = 1; 2083 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 2084 } 2085 } 2086 } 2087 2088 void TreeDelegateGenerateChildren(TreeItem &item) override { 2089 ProcessSP process_sp = GetProcess(); 2090 if (process_sp && process_sp->IsAlive()) { 2091 StateType state = process_sp->GetState(); 2092 if (StateIsStoppedState(state, true)) { 2093 ThreadSP thread_sp = GetThread(item); 2094 if (thread_sp) { 2095 if (m_stop_id == process_sp->GetStopID() && 2096 thread_sp->GetID() == m_tid) 2097 return; // Children are already up to date 2098 if (!m_frame_delegate_sp) { 2099 // Always expand the thread item the first time we show it 2100 m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>(); 2101 } 2102 2103 m_stop_id = process_sp->GetStopID(); 2104 m_tid = thread_sp->GetID(); 2105 2106 TreeItem t(&item, *m_frame_delegate_sp, false); 2107 size_t num_frames = thread_sp->GetStackFrameCount(); 2108 item.Resize(num_frames, t); 2109 for (size_t i = 0; i < num_frames; ++i) { 2110 item[i].SetUserData(thread_sp.get()); 2111 item[i].SetIdentifier(i); 2112 } 2113 } 2114 return; 2115 } 2116 } 2117 item.ClearChildren(); 2118 } 2119 2120 bool TreeDelegateItemSelected(TreeItem &item) override { 2121 ProcessSP process_sp = GetProcess(); 2122 if (process_sp && process_sp->IsAlive()) { 2123 StateType state = process_sp->GetState(); 2124 if (StateIsStoppedState(state, true)) { 2125 ThreadSP thread_sp = GetThread(item); 2126 if (thread_sp) { 2127 ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList(); 2128 std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex()); 2129 ThreadSP selected_thread_sp = thread_list.GetSelectedThread(); 2130 if (selected_thread_sp->GetID() != thread_sp->GetID()) { 2131 thread_list.SetSelectedThreadByID(thread_sp->GetID()); 2132 return true; 2133 } 2134 } 2135 } 2136 } 2137 return false; 2138 } 2139 2140 protected: 2141 Debugger &m_debugger; 2142 std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp; 2143 lldb::user_id_t m_tid; 2144 uint32_t m_stop_id; 2145 FormatEntity::Entry m_format; 2146 }; 2147 2148 class ThreadsTreeDelegate : public TreeDelegate { 2149 public: 2150 ThreadsTreeDelegate(Debugger &debugger) 2151 : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger), 2152 m_stop_id(UINT32_MAX) { 2153 FormatEntity::Parse("process ${process.id}{, name = ${process.name}}", 2154 m_format); 2155 } 2156 2157 ~ThreadsTreeDelegate() override = default; 2158 2159 ProcessSP GetProcess() { 2160 return m_debugger.GetCommandInterpreter() 2161 .GetExecutionContext() 2162 .GetProcessSP(); 2163 } 2164 2165 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 2166 ProcessSP process_sp = GetProcess(); 2167 if (process_sp && process_sp->IsAlive()) { 2168 StreamString strm; 2169 ExecutionContext exe_ctx(process_sp); 2170 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, 2171 nullptr, false, false)) { 2172 int right_pad = 1; 2173 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 2174 } 2175 } 2176 } 2177 2178 void TreeDelegateGenerateChildren(TreeItem &item) override { 2179 ProcessSP process_sp = GetProcess(); 2180 if (process_sp && process_sp->IsAlive()) { 2181 StateType state = process_sp->GetState(); 2182 if (StateIsStoppedState(state, true)) { 2183 const uint32_t stop_id = process_sp->GetStopID(); 2184 if (m_stop_id == stop_id) 2185 return; // Children are already up to date 2186 2187 m_stop_id = stop_id; 2188 2189 if (!m_thread_delegate_sp) { 2190 // Always expand the thread item the first time we show it 2191 // item.Expand(); 2192 m_thread_delegate_sp = 2193 std::make_shared<ThreadTreeDelegate>(m_debugger); 2194 } 2195 2196 TreeItem t(&item, *m_thread_delegate_sp, false); 2197 ThreadList &threads = process_sp->GetThreadList(); 2198 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 2199 size_t num_threads = threads.GetSize(); 2200 item.Resize(num_threads, t); 2201 for (size_t i = 0; i < num_threads; ++i) { 2202 item[i].SetIdentifier(threads.GetThreadAtIndex(i)->GetID()); 2203 item[i].SetMightHaveChildren(true); 2204 } 2205 return; 2206 } 2207 } 2208 item.ClearChildren(); 2209 } 2210 2211 bool TreeDelegateItemSelected(TreeItem &item) override { return false; } 2212 2213 protected: 2214 std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp; 2215 Debugger &m_debugger; 2216 uint32_t m_stop_id; 2217 FormatEntity::Entry m_format; 2218 }; 2219 2220 class ValueObjectListDelegate : public WindowDelegate { 2221 public: 2222 ValueObjectListDelegate() : m_rows() {} 2223 2224 ValueObjectListDelegate(ValueObjectList &valobj_list) 2225 : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0), 2226 m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) { 2227 SetValues(valobj_list); 2228 } 2229 2230 ~ValueObjectListDelegate() override = default; 2231 2232 void SetValues(ValueObjectList &valobj_list) { 2233 m_selected_row = nullptr; 2234 m_selected_row_idx = 0; 2235 m_first_visible_row = 0; 2236 m_num_rows = 0; 2237 m_rows.clear(); 2238 for (auto &valobj_sp : valobj_list.GetObjects()) 2239 m_rows.push_back(Row(valobj_sp, nullptr)); 2240 } 2241 2242 bool WindowDelegateDraw(Window &window, bool force) override { 2243 m_num_rows = 0; 2244 m_min_x = 2; 2245 m_min_y = 1; 2246 m_max_x = window.GetWidth() - 1; 2247 m_max_y = window.GetHeight() - 1; 2248 2249 window.Erase(); 2250 window.DrawTitleBox(window.GetName()); 2251 2252 const int num_visible_rows = NumVisibleRows(); 2253 const int num_rows = CalculateTotalNumberRows(m_rows); 2254 2255 // If we unexpanded while having something selected our total number of 2256 // rows is less than the num visible rows, then make sure we show all the 2257 // rows by setting the first visible row accordingly. 2258 if (m_first_visible_row > 0 && num_rows < num_visible_rows) 2259 m_first_visible_row = 0; 2260 2261 // Make sure the selected row is always visible 2262 if (m_selected_row_idx < m_first_visible_row) 2263 m_first_visible_row = m_selected_row_idx; 2264 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) 2265 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; 2266 2267 DisplayRows(window, m_rows, g_options); 2268 2269 // Get the selected row 2270 m_selected_row = GetRowForRowIndex(m_selected_row_idx); 2271 // Keep the cursor on the selected row so the highlight and the cursor are 2272 // always on the same line 2273 if (m_selected_row) 2274 window.MoveCursor(m_selected_row->x, m_selected_row->y); 2275 2276 return true; // Drawing handled 2277 } 2278 2279 KeyHelp *WindowDelegateGetKeyHelp() override { 2280 static curses::KeyHelp g_source_view_key_help[] = { 2281 {KEY_UP, "Select previous item"}, 2282 {KEY_DOWN, "Select next item"}, 2283 {KEY_RIGHT, "Expand selected item"}, 2284 {KEY_LEFT, "Unexpand selected item or select parent if not expanded"}, 2285 {KEY_PPAGE, "Page up"}, 2286 {KEY_NPAGE, "Page down"}, 2287 {'A', "Format as annotated address"}, 2288 {'b', "Format as binary"}, 2289 {'B', "Format as hex bytes with ASCII"}, 2290 {'c', "Format as character"}, 2291 {'d', "Format as a signed integer"}, 2292 {'D', "Format selected value using the default format for the type"}, 2293 {'f', "Format as float"}, 2294 {'h', "Show help dialog"}, 2295 {'i', "Format as instructions"}, 2296 {'o', "Format as octal"}, 2297 {'p', "Format as pointer"}, 2298 {'s', "Format as C string"}, 2299 {'t', "Toggle showing/hiding type names"}, 2300 {'u', "Format as an unsigned integer"}, 2301 {'x', "Format as hex"}, 2302 {'X', "Format as uppercase hex"}, 2303 {' ', "Toggle item expansion"}, 2304 {',', "Page up"}, 2305 {'.', "Page down"}, 2306 {'\0', nullptr}}; 2307 return g_source_view_key_help; 2308 } 2309 2310 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 2311 switch (c) { 2312 case 'x': 2313 case 'X': 2314 case 'o': 2315 case 's': 2316 case 'u': 2317 case 'd': 2318 case 'D': 2319 case 'i': 2320 case 'A': 2321 case 'p': 2322 case 'c': 2323 case 'b': 2324 case 'B': 2325 case 'f': 2326 // Change the format for the currently selected item 2327 if (m_selected_row) { 2328 auto valobj_sp = m_selected_row->value.GetSP(); 2329 if (valobj_sp) 2330 valobj_sp->SetFormat(FormatForChar(c)); 2331 } 2332 return eKeyHandled; 2333 2334 case 't': 2335 // Toggle showing type names 2336 g_options.show_types = !g_options.show_types; 2337 return eKeyHandled; 2338 2339 case ',': 2340 case KEY_PPAGE: 2341 // Page up key 2342 if (m_first_visible_row > 0) { 2343 if (static_cast<int>(m_first_visible_row) > m_max_y) 2344 m_first_visible_row -= m_max_y; 2345 else 2346 m_first_visible_row = 0; 2347 m_selected_row_idx = m_first_visible_row; 2348 } 2349 return eKeyHandled; 2350 2351 case '.': 2352 case KEY_NPAGE: 2353 // Page down key 2354 if (m_num_rows > static_cast<size_t>(m_max_y)) { 2355 if (m_first_visible_row + m_max_y < m_num_rows) { 2356 m_first_visible_row += m_max_y; 2357 m_selected_row_idx = m_first_visible_row; 2358 } 2359 } 2360 return eKeyHandled; 2361 2362 case KEY_UP: 2363 if (m_selected_row_idx > 0) 2364 --m_selected_row_idx; 2365 return eKeyHandled; 2366 2367 case KEY_DOWN: 2368 if (m_selected_row_idx + 1 < m_num_rows) 2369 ++m_selected_row_idx; 2370 return eKeyHandled; 2371 2372 case KEY_RIGHT: 2373 if (m_selected_row) { 2374 if (!m_selected_row->expanded) 2375 m_selected_row->Expand(); 2376 } 2377 return eKeyHandled; 2378 2379 case KEY_LEFT: 2380 if (m_selected_row) { 2381 if (m_selected_row->expanded) 2382 m_selected_row->Unexpand(); 2383 else if (m_selected_row->parent) 2384 m_selected_row_idx = m_selected_row->parent->row_idx; 2385 } 2386 return eKeyHandled; 2387 2388 case ' ': 2389 // Toggle expansion state when SPACE is pressed 2390 if (m_selected_row) { 2391 if (m_selected_row->expanded) 2392 m_selected_row->Unexpand(); 2393 else 2394 m_selected_row->Expand(); 2395 } 2396 return eKeyHandled; 2397 2398 case 'h': 2399 window.CreateHelpSubwindow(); 2400 return eKeyHandled; 2401 2402 default: 2403 break; 2404 } 2405 return eKeyNotHandled; 2406 } 2407 2408 protected: 2409 std::vector<Row> m_rows; 2410 Row *m_selected_row = nullptr; 2411 uint32_t m_selected_row_idx = 0; 2412 uint32_t m_first_visible_row = 0; 2413 uint32_t m_num_rows = 0; 2414 int m_min_x; 2415 int m_min_y; 2416 int m_max_x = 0; 2417 int m_max_y = 0; 2418 2419 static Format FormatForChar(int c) { 2420 switch (c) { 2421 case 'x': 2422 return eFormatHex; 2423 case 'X': 2424 return eFormatHexUppercase; 2425 case 'o': 2426 return eFormatOctal; 2427 case 's': 2428 return eFormatCString; 2429 case 'u': 2430 return eFormatUnsigned; 2431 case 'd': 2432 return eFormatDecimal; 2433 case 'D': 2434 return eFormatDefault; 2435 case 'i': 2436 return eFormatInstruction; 2437 case 'A': 2438 return eFormatAddressInfo; 2439 case 'p': 2440 return eFormatPointer; 2441 case 'c': 2442 return eFormatChar; 2443 case 'b': 2444 return eFormatBinary; 2445 case 'B': 2446 return eFormatBytesWithASCII; 2447 case 'f': 2448 return eFormatFloat; 2449 } 2450 return eFormatDefault; 2451 } 2452 2453 bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options, 2454 bool highlight, bool last_child) { 2455 ValueObject *valobj = row.value.GetSP().get(); 2456 2457 if (valobj == nullptr) 2458 return false; 2459 2460 const char *type_name = 2461 options.show_types ? valobj->GetTypeName().GetCString() : nullptr; 2462 const char *name = valobj->GetName().GetCString(); 2463 const char *value = valobj->GetValueAsCString(); 2464 const char *summary = valobj->GetSummaryAsCString(); 2465 2466 window.MoveCursor(row.x, row.y); 2467 2468 row.DrawTree(window); 2469 2470 if (highlight) 2471 window.AttributeOn(A_REVERSE); 2472 2473 if (type_name && type_name[0]) 2474 window.PrintfTruncated(1, "(%s) ", type_name); 2475 2476 if (name && name[0]) 2477 window.PutCStringTruncated(1, name); 2478 2479 attr_t changd_attr = 0; 2480 if (valobj->GetValueDidChange()) 2481 changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD; 2482 2483 if (value && value[0]) { 2484 window.PutCStringTruncated(1, " = "); 2485 if (changd_attr) 2486 window.AttributeOn(changd_attr); 2487 window.PutCStringTruncated(1, value); 2488 if (changd_attr) 2489 window.AttributeOff(changd_attr); 2490 } 2491 2492 if (summary && summary[0]) { 2493 window.PutCStringTruncated(1, " "); 2494 if (changd_attr) 2495 window.AttributeOn(changd_attr); 2496 window.PutCStringTruncated(1, summary); 2497 if (changd_attr) 2498 window.AttributeOff(changd_attr); 2499 } 2500 2501 if (highlight) 2502 window.AttributeOff(A_REVERSE); 2503 2504 return true; 2505 } 2506 2507 void DisplayRows(Window &window, std::vector<Row> &rows, 2508 DisplayOptions &options) { 2509 // > 0x25B7 2510 // \/ 0x25BD 2511 2512 bool window_is_active = window.IsActive(); 2513 for (auto &row : rows) { 2514 const bool last_child = row.parent && &rows[rows.size() - 1] == &row; 2515 // Save the row index in each Row structure 2516 row.row_idx = m_num_rows; 2517 if ((m_num_rows >= m_first_visible_row) && 2518 ((m_num_rows - m_first_visible_row) < 2519 static_cast<size_t>(NumVisibleRows()))) { 2520 row.x = m_min_x; 2521 row.y = m_num_rows - m_first_visible_row + 1; 2522 if (DisplayRowObject(window, row, options, 2523 window_is_active && 2524 m_num_rows == m_selected_row_idx, 2525 last_child)) { 2526 ++m_num_rows; 2527 } else { 2528 row.x = 0; 2529 row.y = 0; 2530 } 2531 } else { 2532 row.x = 0; 2533 row.y = 0; 2534 ++m_num_rows; 2535 } 2536 2537 auto &children = row.GetChildren(); 2538 if (row.expanded && !children.empty()) { 2539 DisplayRows(window, children, options); 2540 } 2541 } 2542 } 2543 2544 int CalculateTotalNumberRows(std::vector<Row> &rows) { 2545 int row_count = 0; 2546 for (auto &row : rows) { 2547 ++row_count; 2548 if (row.expanded) 2549 row_count += CalculateTotalNumberRows(row.GetChildren()); 2550 } 2551 return row_count; 2552 } 2553 2554 static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) { 2555 for (auto &row : rows) { 2556 if (row_index == 0) 2557 return &row; 2558 else { 2559 --row_index; 2560 auto &children = row.GetChildren(); 2561 if (row.expanded && !children.empty()) { 2562 Row *result = GetRowForRowIndexImpl(children, row_index); 2563 if (result) 2564 return result; 2565 } 2566 } 2567 } 2568 return nullptr; 2569 } 2570 2571 Row *GetRowForRowIndex(size_t row_index) { 2572 return GetRowForRowIndexImpl(m_rows, row_index); 2573 } 2574 2575 int NumVisibleRows() const { return m_max_y - m_min_y; } 2576 2577 static DisplayOptions g_options; 2578 }; 2579 2580 class FrameVariablesWindowDelegate : public ValueObjectListDelegate { 2581 public: 2582 FrameVariablesWindowDelegate(Debugger &debugger) 2583 : ValueObjectListDelegate(), m_debugger(debugger), 2584 m_frame_block(nullptr) {} 2585 2586 ~FrameVariablesWindowDelegate() override = default; 2587 2588 const char *WindowDelegateGetHelpText() override { 2589 return "Frame variable window keyboard shortcuts:"; 2590 } 2591 2592 bool WindowDelegateDraw(Window &window, bool force) override { 2593 ExecutionContext exe_ctx( 2594 m_debugger.GetCommandInterpreter().GetExecutionContext()); 2595 Process *process = exe_ctx.GetProcessPtr(); 2596 Block *frame_block = nullptr; 2597 StackFrame *frame = nullptr; 2598 2599 if (process) { 2600 StateType state = process->GetState(); 2601 if (StateIsStoppedState(state, true)) { 2602 frame = exe_ctx.GetFramePtr(); 2603 if (frame) 2604 frame_block = frame->GetFrameBlock(); 2605 } else if (StateIsRunningState(state)) { 2606 return true; // Don't do any updating when we are running 2607 } 2608 } 2609 2610 ValueObjectList local_values; 2611 if (frame_block) { 2612 // Only update the variables if they have changed 2613 if (m_frame_block != frame_block) { 2614 m_frame_block = frame_block; 2615 2616 VariableList *locals = frame->GetVariableList(true); 2617 if (locals) { 2618 const DynamicValueType use_dynamic = eDynamicDontRunTarget; 2619 for (const VariableSP &local_sp : *locals) { 2620 ValueObjectSP value_sp = 2621 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic); 2622 if (value_sp) { 2623 ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue(); 2624 if (synthetic_value_sp) 2625 local_values.Append(synthetic_value_sp); 2626 else 2627 local_values.Append(value_sp); 2628 } 2629 } 2630 // Update the values 2631 SetValues(local_values); 2632 } 2633 } 2634 } else { 2635 m_frame_block = nullptr; 2636 // Update the values with an empty list if there is no frame 2637 SetValues(local_values); 2638 } 2639 2640 return ValueObjectListDelegate::WindowDelegateDraw(window, force); 2641 } 2642 2643 protected: 2644 Debugger &m_debugger; 2645 Block *m_frame_block; 2646 }; 2647 2648 class RegistersWindowDelegate : public ValueObjectListDelegate { 2649 public: 2650 RegistersWindowDelegate(Debugger &debugger) 2651 : ValueObjectListDelegate(), m_debugger(debugger) {} 2652 2653 ~RegistersWindowDelegate() override = default; 2654 2655 const char *WindowDelegateGetHelpText() override { 2656 return "Register window keyboard shortcuts:"; 2657 } 2658 2659 bool WindowDelegateDraw(Window &window, bool force) override { 2660 ExecutionContext exe_ctx( 2661 m_debugger.GetCommandInterpreter().GetExecutionContext()); 2662 StackFrame *frame = exe_ctx.GetFramePtr(); 2663 2664 ValueObjectList value_list; 2665 if (frame) { 2666 if (frame->GetStackID() != m_stack_id) { 2667 m_stack_id = frame->GetStackID(); 2668 RegisterContextSP reg_ctx(frame->GetRegisterContext()); 2669 if (reg_ctx) { 2670 const uint32_t num_sets = reg_ctx->GetRegisterSetCount(); 2671 for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) { 2672 value_list.Append( 2673 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx)); 2674 } 2675 } 2676 SetValues(value_list); 2677 } 2678 } else { 2679 Process *process = exe_ctx.GetProcessPtr(); 2680 if (process && process->IsAlive()) 2681 return true; // Don't do any updating if we are running 2682 else { 2683 // Update the values with an empty list if there is no process or the 2684 // process isn't alive anymore 2685 SetValues(value_list); 2686 } 2687 } 2688 return ValueObjectListDelegate::WindowDelegateDraw(window, force); 2689 } 2690 2691 protected: 2692 Debugger &m_debugger; 2693 StackID m_stack_id; 2694 }; 2695 2696 static const char *CursesKeyToCString(int ch) { 2697 static char g_desc[32]; 2698 if (ch >= KEY_F0 && ch < KEY_F0 + 64) { 2699 snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0); 2700 return g_desc; 2701 } 2702 switch (ch) { 2703 case KEY_DOWN: 2704 return "down"; 2705 case KEY_UP: 2706 return "up"; 2707 case KEY_LEFT: 2708 return "left"; 2709 case KEY_RIGHT: 2710 return "right"; 2711 case KEY_HOME: 2712 return "home"; 2713 case KEY_BACKSPACE: 2714 return "backspace"; 2715 case KEY_DL: 2716 return "delete-line"; 2717 case KEY_IL: 2718 return "insert-line"; 2719 case KEY_DC: 2720 return "delete-char"; 2721 case KEY_IC: 2722 return "insert-char"; 2723 case KEY_CLEAR: 2724 return "clear"; 2725 case KEY_EOS: 2726 return "clear-to-eos"; 2727 case KEY_EOL: 2728 return "clear-to-eol"; 2729 case KEY_SF: 2730 return "scroll-forward"; 2731 case KEY_SR: 2732 return "scroll-backward"; 2733 case KEY_NPAGE: 2734 return "page-down"; 2735 case KEY_PPAGE: 2736 return "page-up"; 2737 case KEY_STAB: 2738 return "set-tab"; 2739 case KEY_CTAB: 2740 return "clear-tab"; 2741 case KEY_CATAB: 2742 return "clear-all-tabs"; 2743 case KEY_ENTER: 2744 return "enter"; 2745 case KEY_PRINT: 2746 return "print"; 2747 case KEY_LL: 2748 return "lower-left key"; 2749 case KEY_A1: 2750 return "upper left of keypad"; 2751 case KEY_A3: 2752 return "upper right of keypad"; 2753 case KEY_B2: 2754 return "center of keypad"; 2755 case KEY_C1: 2756 return "lower left of keypad"; 2757 case KEY_C3: 2758 return "lower right of keypad"; 2759 case KEY_BTAB: 2760 return "back-tab key"; 2761 case KEY_BEG: 2762 return "begin key"; 2763 case KEY_CANCEL: 2764 return "cancel key"; 2765 case KEY_CLOSE: 2766 return "close key"; 2767 case KEY_COMMAND: 2768 return "command key"; 2769 case KEY_COPY: 2770 return "copy key"; 2771 case KEY_CREATE: 2772 return "create key"; 2773 case KEY_END: 2774 return "end key"; 2775 case KEY_EXIT: 2776 return "exit key"; 2777 case KEY_FIND: 2778 return "find key"; 2779 case KEY_HELP: 2780 return "help key"; 2781 case KEY_MARK: 2782 return "mark key"; 2783 case KEY_MESSAGE: 2784 return "message key"; 2785 case KEY_MOVE: 2786 return "move key"; 2787 case KEY_NEXT: 2788 return "next key"; 2789 case KEY_OPEN: 2790 return "open key"; 2791 case KEY_OPTIONS: 2792 return "options key"; 2793 case KEY_PREVIOUS: 2794 return "previous key"; 2795 case KEY_REDO: 2796 return "redo key"; 2797 case KEY_REFERENCE: 2798 return "reference key"; 2799 case KEY_REFRESH: 2800 return "refresh key"; 2801 case KEY_REPLACE: 2802 return "replace key"; 2803 case KEY_RESTART: 2804 return "restart key"; 2805 case KEY_RESUME: 2806 return "resume key"; 2807 case KEY_SAVE: 2808 return "save key"; 2809 case KEY_SBEG: 2810 return "shifted begin key"; 2811 case KEY_SCANCEL: 2812 return "shifted cancel key"; 2813 case KEY_SCOMMAND: 2814 return "shifted command key"; 2815 case KEY_SCOPY: 2816 return "shifted copy key"; 2817 case KEY_SCREATE: 2818 return "shifted create key"; 2819 case KEY_SDC: 2820 return "shifted delete-character key"; 2821 case KEY_SDL: 2822 return "shifted delete-line key"; 2823 case KEY_SELECT: 2824 return "select key"; 2825 case KEY_SEND: 2826 return "shifted end key"; 2827 case KEY_SEOL: 2828 return "shifted clear-to-end-of-line key"; 2829 case KEY_SEXIT: 2830 return "shifted exit key"; 2831 case KEY_SFIND: 2832 return "shifted find key"; 2833 case KEY_SHELP: 2834 return "shifted help key"; 2835 case KEY_SHOME: 2836 return "shifted home key"; 2837 case KEY_SIC: 2838 return "shifted insert-character key"; 2839 case KEY_SLEFT: 2840 return "shifted left-arrow key"; 2841 case KEY_SMESSAGE: 2842 return "shifted message key"; 2843 case KEY_SMOVE: 2844 return "shifted move key"; 2845 case KEY_SNEXT: 2846 return "shifted next key"; 2847 case KEY_SOPTIONS: 2848 return "shifted options key"; 2849 case KEY_SPREVIOUS: 2850 return "shifted previous key"; 2851 case KEY_SPRINT: 2852 return "shifted print key"; 2853 case KEY_SREDO: 2854 return "shifted redo key"; 2855 case KEY_SREPLACE: 2856 return "shifted replace key"; 2857 case KEY_SRIGHT: 2858 return "shifted right-arrow key"; 2859 case KEY_SRSUME: 2860 return "shifted resume key"; 2861 case KEY_SSAVE: 2862 return "shifted save key"; 2863 case KEY_SSUSPEND: 2864 return "shifted suspend key"; 2865 case KEY_SUNDO: 2866 return "shifted undo key"; 2867 case KEY_SUSPEND: 2868 return "suspend key"; 2869 case KEY_UNDO: 2870 return "undo key"; 2871 case KEY_MOUSE: 2872 return "Mouse event has occurred"; 2873 case KEY_RESIZE: 2874 return "Terminal resize event"; 2875 #ifdef KEY_EVENT 2876 case KEY_EVENT: 2877 return "We were interrupted by an event"; 2878 #endif 2879 case KEY_RETURN: 2880 return "return"; 2881 case ' ': 2882 return "space"; 2883 case '\t': 2884 return "tab"; 2885 case KEY_ESCAPE: 2886 return "escape"; 2887 default: 2888 if (llvm::isPrint(ch)) 2889 snprintf(g_desc, sizeof(g_desc), "%c", ch); 2890 else 2891 snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch); 2892 return g_desc; 2893 } 2894 return nullptr; 2895 } 2896 2897 HelpDialogDelegate::HelpDialogDelegate(const char *text, 2898 KeyHelp *key_help_array) 2899 : m_text(), m_first_visible_line(0) { 2900 if (text && text[0]) { 2901 m_text.SplitIntoLines(text); 2902 m_text.AppendString(""); 2903 } 2904 if (key_help_array) { 2905 for (KeyHelp *key = key_help_array; key->ch; ++key) { 2906 StreamString key_description; 2907 key_description.Printf("%10s - %s", CursesKeyToCString(key->ch), 2908 key->description); 2909 m_text.AppendString(key_description.GetString()); 2910 } 2911 } 2912 } 2913 2914 HelpDialogDelegate::~HelpDialogDelegate() = default; 2915 2916 bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) { 2917 window.Erase(); 2918 const int window_height = window.GetHeight(); 2919 int x = 2; 2920 int y = 1; 2921 const int min_y = y; 2922 const int max_y = window_height - 1 - y; 2923 const size_t num_visible_lines = max_y - min_y + 1; 2924 const size_t num_lines = m_text.GetSize(); 2925 const char *bottom_message; 2926 if (num_lines <= num_visible_lines) 2927 bottom_message = "Press any key to exit"; 2928 else 2929 bottom_message = "Use arrows to scroll, any other key to exit"; 2930 window.DrawTitleBox(window.GetName(), bottom_message); 2931 while (y <= max_y) { 2932 window.MoveCursor(x, y); 2933 window.PutCStringTruncated( 2934 1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y)); 2935 ++y; 2936 } 2937 return true; 2938 } 2939 2940 HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window, 2941 int key) { 2942 bool done = false; 2943 const size_t num_lines = m_text.GetSize(); 2944 const size_t num_visible_lines = window.GetHeight() - 2; 2945 2946 if (num_lines <= num_visible_lines) { 2947 done = true; 2948 // If we have all lines visible and don't need scrolling, then any key 2949 // press will cause us to exit 2950 } else { 2951 switch (key) { 2952 case KEY_UP: 2953 if (m_first_visible_line > 0) 2954 --m_first_visible_line; 2955 break; 2956 2957 case KEY_DOWN: 2958 if (m_first_visible_line + num_visible_lines < num_lines) 2959 ++m_first_visible_line; 2960 break; 2961 2962 case KEY_PPAGE: 2963 case ',': 2964 if (m_first_visible_line > 0) { 2965 if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines) 2966 m_first_visible_line -= num_visible_lines; 2967 else 2968 m_first_visible_line = 0; 2969 } 2970 break; 2971 2972 case KEY_NPAGE: 2973 case '.': 2974 if (m_first_visible_line + num_visible_lines < num_lines) { 2975 m_first_visible_line += num_visible_lines; 2976 if (static_cast<size_t>(m_first_visible_line) > num_lines) 2977 m_first_visible_line = num_lines - num_visible_lines; 2978 } 2979 break; 2980 2981 default: 2982 done = true; 2983 break; 2984 } 2985 } 2986 if (done) 2987 window.GetParent()->RemoveSubWindow(&window); 2988 return eKeyHandled; 2989 } 2990 2991 class ApplicationDelegate : public WindowDelegate, public MenuDelegate { 2992 public: 2993 enum { 2994 eMenuID_LLDB = 1, 2995 eMenuID_LLDBAbout, 2996 eMenuID_LLDBExit, 2997 2998 eMenuID_Target, 2999 eMenuID_TargetCreate, 3000 eMenuID_TargetDelete, 3001 3002 eMenuID_Process, 3003 eMenuID_ProcessAttach, 3004 eMenuID_ProcessDetachResume, 3005 eMenuID_ProcessDetachSuspended, 3006 eMenuID_ProcessLaunch, 3007 eMenuID_ProcessContinue, 3008 eMenuID_ProcessHalt, 3009 eMenuID_ProcessKill, 3010 3011 eMenuID_Thread, 3012 eMenuID_ThreadStepIn, 3013 eMenuID_ThreadStepOver, 3014 eMenuID_ThreadStepOut, 3015 3016 eMenuID_View, 3017 eMenuID_ViewBacktrace, 3018 eMenuID_ViewRegisters, 3019 eMenuID_ViewSource, 3020 eMenuID_ViewVariables, 3021 3022 eMenuID_Help, 3023 eMenuID_HelpGUIHelp 3024 }; 3025 3026 ApplicationDelegate(Application &app, Debugger &debugger) 3027 : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {} 3028 3029 ~ApplicationDelegate() override = default; 3030 3031 bool WindowDelegateDraw(Window &window, bool force) override { 3032 return false; // Drawing not handled, let standard window drawing happen 3033 } 3034 3035 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { 3036 switch (key) { 3037 case '\t': 3038 window.SelectNextWindowAsActive(); 3039 return eKeyHandled; 3040 3041 case KEY_BTAB: 3042 window.SelectPreviousWindowAsActive(); 3043 return eKeyHandled; 3044 3045 case 'h': 3046 window.CreateHelpSubwindow(); 3047 return eKeyHandled; 3048 3049 case KEY_ESCAPE: 3050 return eQuitApplication; 3051 3052 default: 3053 break; 3054 } 3055 return eKeyNotHandled; 3056 } 3057 3058 const char *WindowDelegateGetHelpText() override { 3059 return "Welcome to the LLDB curses GUI.\n\n" 3060 "Press the TAB key to change the selected view.\n" 3061 "Each view has its own keyboard shortcuts, press 'h' to open a " 3062 "dialog to display them.\n\n" 3063 "Common key bindings for all views:"; 3064 } 3065 3066 KeyHelp *WindowDelegateGetKeyHelp() override { 3067 static curses::KeyHelp g_source_view_key_help[] = { 3068 {'\t', "Select next view"}, 3069 {KEY_BTAB, "Select previous view"}, 3070 {'h', "Show help dialog with view specific key bindings"}, 3071 {',', "Page up"}, 3072 {'.', "Page down"}, 3073 {KEY_UP, "Select previous"}, 3074 {KEY_DOWN, "Select next"}, 3075 {KEY_LEFT, "Unexpand or select parent"}, 3076 {KEY_RIGHT, "Expand"}, 3077 {KEY_PPAGE, "Page up"}, 3078 {KEY_NPAGE, "Page down"}, 3079 {'\0', nullptr}}; 3080 return g_source_view_key_help; 3081 } 3082 3083 MenuActionResult MenuDelegateAction(Menu &menu) override { 3084 switch (menu.GetIdentifier()) { 3085 case eMenuID_ThreadStepIn: { 3086 ExecutionContext exe_ctx = 3087 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3088 if (exe_ctx.HasThreadScope()) { 3089 Process *process = exe_ctx.GetProcessPtr(); 3090 if (process && process->IsAlive() && 3091 StateIsStoppedState(process->GetState(), true)) 3092 exe_ctx.GetThreadRef().StepIn(true); 3093 } 3094 } 3095 return MenuActionResult::Handled; 3096 3097 case eMenuID_ThreadStepOut: { 3098 ExecutionContext exe_ctx = 3099 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3100 if (exe_ctx.HasThreadScope()) { 3101 Process *process = exe_ctx.GetProcessPtr(); 3102 if (process && process->IsAlive() && 3103 StateIsStoppedState(process->GetState(), true)) 3104 exe_ctx.GetThreadRef().StepOut(); 3105 } 3106 } 3107 return MenuActionResult::Handled; 3108 3109 case eMenuID_ThreadStepOver: { 3110 ExecutionContext exe_ctx = 3111 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3112 if (exe_ctx.HasThreadScope()) { 3113 Process *process = exe_ctx.GetProcessPtr(); 3114 if (process && process->IsAlive() && 3115 StateIsStoppedState(process->GetState(), true)) 3116 exe_ctx.GetThreadRef().StepOver(true); 3117 } 3118 } 3119 return MenuActionResult::Handled; 3120 3121 case eMenuID_ProcessContinue: { 3122 ExecutionContext exe_ctx = 3123 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3124 if (exe_ctx.HasProcessScope()) { 3125 Process *process = exe_ctx.GetProcessPtr(); 3126 if (process && process->IsAlive() && 3127 StateIsStoppedState(process->GetState(), true)) 3128 process->Resume(); 3129 } 3130 } 3131 return MenuActionResult::Handled; 3132 3133 case eMenuID_ProcessKill: { 3134 ExecutionContext exe_ctx = 3135 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3136 if (exe_ctx.HasProcessScope()) { 3137 Process *process = exe_ctx.GetProcessPtr(); 3138 if (process && process->IsAlive()) 3139 process->Destroy(false); 3140 } 3141 } 3142 return MenuActionResult::Handled; 3143 3144 case eMenuID_ProcessHalt: { 3145 ExecutionContext exe_ctx = 3146 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3147 if (exe_ctx.HasProcessScope()) { 3148 Process *process = exe_ctx.GetProcessPtr(); 3149 if (process && process->IsAlive()) 3150 process->Halt(); 3151 } 3152 } 3153 return MenuActionResult::Handled; 3154 3155 case eMenuID_ProcessDetachResume: 3156 case eMenuID_ProcessDetachSuspended: { 3157 ExecutionContext exe_ctx = 3158 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3159 if (exe_ctx.HasProcessScope()) { 3160 Process *process = exe_ctx.GetProcessPtr(); 3161 if (process && process->IsAlive()) 3162 process->Detach(menu.GetIdentifier() == 3163 eMenuID_ProcessDetachSuspended); 3164 } 3165 } 3166 return MenuActionResult::Handled; 3167 3168 case eMenuID_Process: { 3169 // Populate the menu with all of the threads if the process is stopped 3170 // when the Process menu gets selected and is about to display its 3171 // submenu. 3172 Menus &submenus = menu.GetSubmenus(); 3173 ExecutionContext exe_ctx = 3174 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3175 Process *process = exe_ctx.GetProcessPtr(); 3176 if (process && process->IsAlive() && 3177 StateIsStoppedState(process->GetState(), true)) { 3178 if (submenus.size() == 7) 3179 menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 3180 else if (submenus.size() > 8) 3181 submenus.erase(submenus.begin() + 8, submenus.end()); 3182 3183 ThreadList &threads = process->GetThreadList(); 3184 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 3185 size_t num_threads = threads.GetSize(); 3186 for (size_t i = 0; i < num_threads; ++i) { 3187 ThreadSP thread_sp = threads.GetThreadAtIndex(i); 3188 char menu_char = '\0'; 3189 if (i < 9) 3190 menu_char = '1' + i; 3191 StreamString thread_menu_title; 3192 thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID()); 3193 const char *thread_name = thread_sp->GetName(); 3194 if (thread_name && thread_name[0]) 3195 thread_menu_title.Printf(" %s", thread_name); 3196 else { 3197 const char *queue_name = thread_sp->GetQueueName(); 3198 if (queue_name && queue_name[0]) 3199 thread_menu_title.Printf(" %s", queue_name); 3200 } 3201 menu.AddSubmenu( 3202 MenuSP(new Menu(thread_menu_title.GetString().str().c_str(), 3203 nullptr, menu_char, thread_sp->GetID()))); 3204 } 3205 } else if (submenus.size() > 7) { 3206 // Remove the separator and any other thread submenu items that were 3207 // previously added 3208 submenus.erase(submenus.begin() + 7, submenus.end()); 3209 } 3210 // Since we are adding and removing items we need to recalculate the name 3211 // lengths 3212 menu.RecalculateNameLengths(); 3213 } 3214 return MenuActionResult::Handled; 3215 3216 case eMenuID_ViewVariables: { 3217 WindowSP main_window_sp = m_app.GetMainWindow(); 3218 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 3219 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 3220 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 3221 const Rect source_bounds = source_window_sp->GetBounds(); 3222 3223 if (variables_window_sp) { 3224 const Rect variables_bounds = variables_window_sp->GetBounds(); 3225 3226 main_window_sp->RemoveSubWindow(variables_window_sp.get()); 3227 3228 if (registers_window_sp) { 3229 // We have a registers window, so give all the area back to the 3230 // registers window 3231 Rect registers_bounds = variables_bounds; 3232 registers_bounds.size.width = source_bounds.size.width; 3233 registers_window_sp->SetBounds(registers_bounds); 3234 } else { 3235 // We have no registers window showing so give the bottom area back 3236 // to the source view 3237 source_window_sp->Resize(source_bounds.size.width, 3238 source_bounds.size.height + 3239 variables_bounds.size.height); 3240 } 3241 } else { 3242 Rect new_variables_rect; 3243 if (registers_window_sp) { 3244 // We have a registers window so split the area of the registers 3245 // window into two columns where the left hand side will be the 3246 // variables and the right hand side will be the registers 3247 const Rect variables_bounds = registers_window_sp->GetBounds(); 3248 Rect new_registers_rect; 3249 variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect, 3250 new_registers_rect); 3251 registers_window_sp->SetBounds(new_registers_rect); 3252 } else { 3253 // No registers window, grab the bottom part of the source window 3254 Rect new_source_rect; 3255 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, 3256 new_variables_rect); 3257 source_window_sp->SetBounds(new_source_rect); 3258 } 3259 WindowSP new_window_sp = main_window_sp->CreateSubWindow( 3260 "Variables", new_variables_rect, false); 3261 new_window_sp->SetDelegate( 3262 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 3263 } 3264 touchwin(stdscr); 3265 } 3266 return MenuActionResult::Handled; 3267 3268 case eMenuID_ViewRegisters: { 3269 WindowSP main_window_sp = m_app.GetMainWindow(); 3270 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 3271 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 3272 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 3273 const Rect source_bounds = source_window_sp->GetBounds(); 3274 3275 if (registers_window_sp) { 3276 if (variables_window_sp) { 3277 const Rect variables_bounds = variables_window_sp->GetBounds(); 3278 3279 // We have a variables window, so give all the area back to the 3280 // variables window 3281 variables_window_sp->Resize(variables_bounds.size.width + 3282 registers_window_sp->GetWidth(), 3283 variables_bounds.size.height); 3284 } else { 3285 // We have no variables window showing so give the bottom area back 3286 // to the source view 3287 source_window_sp->Resize(source_bounds.size.width, 3288 source_bounds.size.height + 3289 registers_window_sp->GetHeight()); 3290 } 3291 main_window_sp->RemoveSubWindow(registers_window_sp.get()); 3292 } else { 3293 Rect new_regs_rect; 3294 if (variables_window_sp) { 3295 // We have a variables window, split it into two columns where the 3296 // left hand side will be the variables and the right hand side will 3297 // be the registers 3298 const Rect variables_bounds = variables_window_sp->GetBounds(); 3299 Rect new_vars_rect; 3300 variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect, 3301 new_regs_rect); 3302 variables_window_sp->SetBounds(new_vars_rect); 3303 } else { 3304 // No variables window, grab the bottom part of the source window 3305 Rect new_source_rect; 3306 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, 3307 new_regs_rect); 3308 source_window_sp->SetBounds(new_source_rect); 3309 } 3310 WindowSP new_window_sp = 3311 main_window_sp->CreateSubWindow("Registers", new_regs_rect, false); 3312 new_window_sp->SetDelegate( 3313 WindowDelegateSP(new RegistersWindowDelegate(m_debugger))); 3314 } 3315 touchwin(stdscr); 3316 } 3317 return MenuActionResult::Handled; 3318 3319 case eMenuID_HelpGUIHelp: 3320 m_app.GetMainWindow()->CreateHelpSubwindow(); 3321 return MenuActionResult::Handled; 3322 3323 default: 3324 break; 3325 } 3326 3327 return MenuActionResult::NotHandled; 3328 } 3329 3330 protected: 3331 Application &m_app; 3332 Debugger &m_debugger; 3333 }; 3334 3335 class StatusBarWindowDelegate : public WindowDelegate { 3336 public: 3337 StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) { 3338 FormatEntity::Parse("Thread: ${thread.id%tid}", m_format); 3339 } 3340 3341 ~StatusBarWindowDelegate() override = default; 3342 3343 bool WindowDelegateDraw(Window &window, bool force) override { 3344 ExecutionContext exe_ctx = 3345 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3346 Process *process = exe_ctx.GetProcessPtr(); 3347 Thread *thread = exe_ctx.GetThreadPtr(); 3348 StackFrame *frame = exe_ctx.GetFramePtr(); 3349 window.Erase(); 3350 window.SetBackground(BlackOnWhite); 3351 window.MoveCursor(0, 0); 3352 if (process) { 3353 const StateType state = process->GetState(); 3354 window.Printf("Process: %5" PRIu64 " %10s", process->GetID(), 3355 StateAsCString(state)); 3356 3357 if (StateIsStoppedState(state, true)) { 3358 StreamString strm; 3359 if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, 3360 nullptr, nullptr, false, false)) { 3361 window.MoveCursor(40, 0); 3362 window.PutCStringTruncated(1, strm.GetString().str().c_str()); 3363 } 3364 3365 window.MoveCursor(60, 0); 3366 if (frame) 3367 window.Printf("Frame: %3u PC = 0x%16.16" PRIx64, 3368 frame->GetFrameIndex(), 3369 frame->GetFrameCodeAddress().GetOpcodeLoadAddress( 3370 exe_ctx.GetTargetPtr())); 3371 } else if (state == eStateExited) { 3372 const char *exit_desc = process->GetExitDescription(); 3373 const int exit_status = process->GetExitStatus(); 3374 if (exit_desc && exit_desc[0]) 3375 window.Printf(" with status = %i (%s)", exit_status, exit_desc); 3376 else 3377 window.Printf(" with status = %i", exit_status); 3378 } 3379 } 3380 return true; 3381 } 3382 3383 protected: 3384 Debugger &m_debugger; 3385 FormatEntity::Entry m_format; 3386 }; 3387 3388 class SourceFileWindowDelegate : public WindowDelegate { 3389 public: 3390 SourceFileWindowDelegate(Debugger &debugger) 3391 : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(), 3392 m_disassembly_scope(nullptr), m_disassembly_sp(), m_disassembly_range(), 3393 m_title(), m_tid(LLDB_INVALID_THREAD_ID), m_line_width(4), 3394 m_selected_line(0), m_pc_line(0), m_stop_id(0), m_frame_idx(UINT32_MAX), 3395 m_first_visible_line(0), m_first_visible_column(0), m_min_x(0), 3396 m_min_y(0), m_max_x(0), m_max_y(0) {} 3397 3398 ~SourceFileWindowDelegate() override = default; 3399 3400 void Update(const SymbolContext &sc) { m_sc = sc; } 3401 3402 uint32_t NumVisibleLines() const { return m_max_y - m_min_y; } 3403 3404 const char *WindowDelegateGetHelpText() override { 3405 return "Source/Disassembly window keyboard shortcuts:"; 3406 } 3407 3408 KeyHelp *WindowDelegateGetKeyHelp() override { 3409 static curses::KeyHelp g_source_view_key_help[] = { 3410 {KEY_RETURN, "Run to selected line with one shot breakpoint"}, 3411 {KEY_UP, "Select previous source line"}, 3412 {KEY_DOWN, "Select next source line"}, 3413 {KEY_LEFT, "Scroll to the left"}, 3414 {KEY_RIGHT, "Scroll to the right"}, 3415 {KEY_PPAGE, "Page up"}, 3416 {KEY_NPAGE, "Page down"}, 3417 {'b', "Set breakpoint on selected source/disassembly line"}, 3418 {'c', "Continue process"}, 3419 {'D', "Detach with process suspended"}, 3420 {'h', "Show help dialog"}, 3421 {'n', "Step over (source line)"}, 3422 {'N', "Step over (single instruction)"}, 3423 {'f', "Step out (finish)"}, 3424 {'s', "Step in (source line)"}, 3425 {'S', "Step in (single instruction)"}, 3426 {'u', "Frame up"}, 3427 {'d', "Frame down"}, 3428 {',', "Page up"}, 3429 {'.', "Page down"}, 3430 {'\0', nullptr}}; 3431 return g_source_view_key_help; 3432 } 3433 3434 bool WindowDelegateDraw(Window &window, bool force) override { 3435 ExecutionContext exe_ctx = 3436 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3437 Process *process = exe_ctx.GetProcessPtr(); 3438 Thread *thread = nullptr; 3439 3440 bool update_location = false; 3441 if (process) { 3442 StateType state = process->GetState(); 3443 if (StateIsStoppedState(state, true)) { 3444 // We are stopped, so it is ok to 3445 update_location = true; 3446 } 3447 } 3448 3449 m_min_x = 1; 3450 m_min_y = 2; 3451 m_max_x = window.GetMaxX() - 1; 3452 m_max_y = window.GetMaxY() - 1; 3453 3454 const uint32_t num_visible_lines = NumVisibleLines(); 3455 StackFrameSP frame_sp; 3456 bool set_selected_line_to_pc = false; 3457 3458 if (update_location) { 3459 const bool process_alive = process ? process->IsAlive() : false; 3460 bool thread_changed = false; 3461 if (process_alive) { 3462 thread = exe_ctx.GetThreadPtr(); 3463 if (thread) { 3464 frame_sp = thread->GetSelectedFrame(); 3465 auto tid = thread->GetID(); 3466 thread_changed = tid != m_tid; 3467 m_tid = tid; 3468 } else { 3469 if (m_tid != LLDB_INVALID_THREAD_ID) { 3470 thread_changed = true; 3471 m_tid = LLDB_INVALID_THREAD_ID; 3472 } 3473 } 3474 } 3475 const uint32_t stop_id = process ? process->GetStopID() : 0; 3476 const bool stop_id_changed = stop_id != m_stop_id; 3477 bool frame_changed = false; 3478 m_stop_id = stop_id; 3479 m_title.Clear(); 3480 if (frame_sp) { 3481 m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything); 3482 if (m_sc.module_sp) { 3483 m_title.Printf( 3484 "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString()); 3485 ConstString func_name = m_sc.GetFunctionName(); 3486 if (func_name) 3487 m_title.Printf("`%s", func_name.GetCString()); 3488 } 3489 const uint32_t frame_idx = frame_sp->GetFrameIndex(); 3490 frame_changed = frame_idx != m_frame_idx; 3491 m_frame_idx = frame_idx; 3492 } else { 3493 m_sc.Clear(true); 3494 frame_changed = m_frame_idx != UINT32_MAX; 3495 m_frame_idx = UINT32_MAX; 3496 } 3497 3498 const bool context_changed = 3499 thread_changed || frame_changed || stop_id_changed; 3500 3501 if (process_alive) { 3502 if (m_sc.line_entry.IsValid()) { 3503 m_pc_line = m_sc.line_entry.line; 3504 if (m_pc_line != UINT32_MAX) 3505 --m_pc_line; // Convert to zero based line number... 3506 // Update the selected line if the stop ID changed... 3507 if (context_changed) 3508 m_selected_line = m_pc_line; 3509 3510 if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) { 3511 // Same file, nothing to do, we should either have the lines or not 3512 // (source file missing) 3513 if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) { 3514 if (m_selected_line >= m_first_visible_line + num_visible_lines) 3515 m_first_visible_line = m_selected_line - 10; 3516 } else { 3517 if (m_selected_line > 10) 3518 m_first_visible_line = m_selected_line - 10; 3519 else 3520 m_first_visible_line = 0; 3521 } 3522 } else { 3523 // File changed, set selected line to the line with the PC 3524 m_selected_line = m_pc_line; 3525 m_file_sp = 3526 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file); 3527 if (m_file_sp) { 3528 const size_t num_lines = m_file_sp->GetNumLines(); 3529 m_line_width = 1; 3530 for (size_t n = num_lines; n >= 10; n = n / 10) 3531 ++m_line_width; 3532 3533 if (num_lines < num_visible_lines || 3534 m_selected_line < num_visible_lines) 3535 m_first_visible_line = 0; 3536 else 3537 m_first_visible_line = m_selected_line - 10; 3538 } 3539 } 3540 } else { 3541 m_file_sp.reset(); 3542 } 3543 3544 if (!m_file_sp || m_file_sp->GetNumLines() == 0) { 3545 // Show disassembly 3546 bool prefer_file_cache = false; 3547 if (m_sc.function) { 3548 if (m_disassembly_scope != m_sc.function) { 3549 m_disassembly_scope = m_sc.function; 3550 m_disassembly_sp = m_sc.function->GetInstructions( 3551 exe_ctx, nullptr, !prefer_file_cache); 3552 if (m_disassembly_sp) { 3553 set_selected_line_to_pc = true; 3554 m_disassembly_range = m_sc.function->GetAddressRange(); 3555 } else { 3556 m_disassembly_range.Clear(); 3557 } 3558 } else { 3559 set_selected_line_to_pc = context_changed; 3560 } 3561 } else if (m_sc.symbol) { 3562 if (m_disassembly_scope != m_sc.symbol) { 3563 m_disassembly_scope = m_sc.symbol; 3564 m_disassembly_sp = m_sc.symbol->GetInstructions( 3565 exe_ctx, nullptr, prefer_file_cache); 3566 if (m_disassembly_sp) { 3567 set_selected_line_to_pc = true; 3568 m_disassembly_range.GetBaseAddress() = 3569 m_sc.symbol->GetAddress(); 3570 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize()); 3571 } else { 3572 m_disassembly_range.Clear(); 3573 } 3574 } else { 3575 set_selected_line_to_pc = context_changed; 3576 } 3577 } 3578 } 3579 } else { 3580 m_pc_line = UINT32_MAX; 3581 } 3582 } 3583 3584 const int window_width = window.GetWidth(); 3585 window.Erase(); 3586 window.DrawTitleBox("Sources"); 3587 if (!m_title.GetString().empty()) { 3588 window.AttributeOn(A_REVERSE); 3589 window.MoveCursor(1, 1); 3590 window.PutChar(' '); 3591 window.PutCStringTruncated(1, m_title.GetString().str().c_str()); 3592 int x = window.GetCursorX(); 3593 if (x < window_width - 1) { 3594 window.Printf("%*s", window_width - x - 1, ""); 3595 } 3596 window.AttributeOff(A_REVERSE); 3597 } 3598 3599 Target *target = exe_ctx.GetTargetPtr(); 3600 const size_t num_source_lines = GetNumSourceLines(); 3601 if (num_source_lines > 0) { 3602 // Display source 3603 BreakpointLines bp_lines; 3604 if (target) { 3605 BreakpointList &bp_list = target->GetBreakpointList(); 3606 const size_t num_bps = bp_list.GetSize(); 3607 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 3608 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 3609 const size_t num_bps_locs = bp_sp->GetNumLocations(); 3610 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 3611 BreakpointLocationSP bp_loc_sp = 3612 bp_sp->GetLocationAtIndex(bp_loc_idx); 3613 LineEntry bp_loc_line_entry; 3614 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( 3615 bp_loc_line_entry)) { 3616 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) { 3617 bp_lines.insert(bp_loc_line_entry.line); 3618 } 3619 } 3620 } 3621 } 3622 } 3623 3624 const attr_t selected_highlight_attr = A_REVERSE; 3625 const attr_t pc_highlight_attr = COLOR_PAIR(BlackOnBlue); 3626 3627 for (size_t i = 0; i < num_visible_lines; ++i) { 3628 const uint32_t curr_line = m_first_visible_line + i; 3629 if (curr_line < num_source_lines) { 3630 const int line_y = m_min_y + i; 3631 window.MoveCursor(1, line_y); 3632 const bool is_pc_line = curr_line == m_pc_line; 3633 const bool line_is_selected = m_selected_line == curr_line; 3634 // Highlight the line as the PC line first, then if the selected line 3635 // isn't the same as the PC line, highlight it differently 3636 attr_t highlight_attr = 0; 3637 attr_t bp_attr = 0; 3638 if (is_pc_line) 3639 highlight_attr = pc_highlight_attr; 3640 else if (line_is_selected) 3641 highlight_attr = selected_highlight_attr; 3642 3643 if (bp_lines.find(curr_line + 1) != bp_lines.end()) 3644 bp_attr = COLOR_PAIR(BlackOnWhite); 3645 3646 if (bp_attr) 3647 window.AttributeOn(bp_attr); 3648 3649 window.Printf(" %*u ", m_line_width, curr_line + 1); 3650 3651 if (bp_attr) 3652 window.AttributeOff(bp_attr); 3653 3654 window.PutChar(ACS_VLINE); 3655 // Mark the line with the PC with a diamond 3656 if (is_pc_line) 3657 window.PutChar(ACS_DIAMOND); 3658 else 3659 window.PutChar(' '); 3660 3661 if (highlight_attr) 3662 window.AttributeOn(highlight_attr); 3663 3664 StreamString lineStream; 3665 m_file_sp->DisplaySourceLines(curr_line + 1, {}, 0, 0, &lineStream); 3666 StringRef line = lineStream.GetString(); 3667 if (line.endswith("\n")) 3668 line = line.drop_back(); 3669 bool wasWritten = window.OutputColoredStringTruncated( 3670 1, line, m_first_visible_column, line_is_selected); 3671 if (line_is_selected && !wasWritten) { 3672 // Draw an empty space to show the selected line if empty, 3673 // or draw '<' if nothing is visible because of scrolling too much 3674 // to the right. 3675 window.PutCStringTruncated( 3676 1, line.empty() && m_first_visible_column == 0 ? " " : "<"); 3677 } 3678 3679 if (is_pc_line && frame_sp && 3680 frame_sp->GetConcreteFrameIndex() == 0) { 3681 StopInfoSP stop_info_sp; 3682 if (thread) 3683 stop_info_sp = thread->GetStopInfo(); 3684 if (stop_info_sp) { 3685 const char *stop_description = stop_info_sp->GetDescription(); 3686 if (stop_description && stop_description[0]) { 3687 size_t stop_description_len = strlen(stop_description); 3688 int desc_x = window_width - stop_description_len - 16; 3689 if (desc_x - window.GetCursorX() > 0) 3690 window.Printf("%*s", desc_x - window.GetCursorX(), ""); 3691 window.MoveCursor(window_width - stop_description_len - 16, 3692 line_y); 3693 const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue); 3694 window.AttributeOn(stop_reason_attr); 3695 window.PrintfTruncated(1, " <<< Thread %u: %s ", 3696 thread->GetIndexID(), stop_description); 3697 window.AttributeOff(stop_reason_attr); 3698 } 3699 } else { 3700 window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); 3701 } 3702 } 3703 if (highlight_attr) 3704 window.AttributeOff(highlight_attr); 3705 } else { 3706 break; 3707 } 3708 } 3709 } else { 3710 size_t num_disassembly_lines = GetNumDisassemblyLines(); 3711 if (num_disassembly_lines > 0) { 3712 // Display disassembly 3713 BreakpointAddrs bp_file_addrs; 3714 Target *target = exe_ctx.GetTargetPtr(); 3715 if (target) { 3716 BreakpointList &bp_list = target->GetBreakpointList(); 3717 const size_t num_bps = bp_list.GetSize(); 3718 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 3719 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 3720 const size_t num_bps_locs = bp_sp->GetNumLocations(); 3721 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; 3722 ++bp_loc_idx) { 3723 BreakpointLocationSP bp_loc_sp = 3724 bp_sp->GetLocationAtIndex(bp_loc_idx); 3725 LineEntry bp_loc_line_entry; 3726 const lldb::addr_t file_addr = 3727 bp_loc_sp->GetAddress().GetFileAddress(); 3728 if (file_addr != LLDB_INVALID_ADDRESS) { 3729 if (m_disassembly_range.ContainsFileAddress(file_addr)) 3730 bp_file_addrs.insert(file_addr); 3731 } 3732 } 3733 } 3734 } 3735 3736 const attr_t selected_highlight_attr = A_REVERSE; 3737 const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue); 3738 3739 StreamString strm; 3740 3741 InstructionList &insts = m_disassembly_sp->GetInstructionList(); 3742 Address pc_address; 3743 3744 if (frame_sp) 3745 pc_address = frame_sp->GetFrameCodeAddress(); 3746 const uint32_t pc_idx = 3747 pc_address.IsValid() 3748 ? insts.GetIndexOfInstructionAtAddress(pc_address) 3749 : UINT32_MAX; 3750 if (set_selected_line_to_pc) { 3751 m_selected_line = pc_idx; 3752 } 3753 3754 const uint32_t non_visible_pc_offset = (num_visible_lines / 5); 3755 if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines) 3756 m_first_visible_line = 0; 3757 3758 if (pc_idx < num_disassembly_lines) { 3759 if (pc_idx < static_cast<uint32_t>(m_first_visible_line) || 3760 pc_idx >= m_first_visible_line + num_visible_lines) 3761 m_first_visible_line = pc_idx - non_visible_pc_offset; 3762 } 3763 3764 for (size_t i = 0; i < num_visible_lines; ++i) { 3765 const uint32_t inst_idx = m_first_visible_line + i; 3766 Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get(); 3767 if (!inst) 3768 break; 3769 3770 const int line_y = m_min_y + i; 3771 window.MoveCursor(1, line_y); 3772 const bool is_pc_line = frame_sp && inst_idx == pc_idx; 3773 const bool line_is_selected = m_selected_line == inst_idx; 3774 // Highlight the line as the PC line first, then if the selected line 3775 // isn't the same as the PC line, highlight it differently 3776 attr_t highlight_attr = 0; 3777 attr_t bp_attr = 0; 3778 if (is_pc_line) 3779 highlight_attr = pc_highlight_attr; 3780 else if (line_is_selected) 3781 highlight_attr = selected_highlight_attr; 3782 3783 if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) != 3784 bp_file_addrs.end()) 3785 bp_attr = COLOR_PAIR(BlackOnWhite); 3786 3787 if (bp_attr) 3788 window.AttributeOn(bp_attr); 3789 3790 window.Printf(" 0x%16.16llx ", 3791 static_cast<unsigned long long>( 3792 inst->GetAddress().GetLoadAddress(target))); 3793 3794 if (bp_attr) 3795 window.AttributeOff(bp_attr); 3796 3797 window.PutChar(ACS_VLINE); 3798 // Mark the line with the PC with a diamond 3799 if (is_pc_line) 3800 window.PutChar(ACS_DIAMOND); 3801 else 3802 window.PutChar(' '); 3803 3804 if (highlight_attr) 3805 window.AttributeOn(highlight_attr); 3806 3807 const char *mnemonic = inst->GetMnemonic(&exe_ctx); 3808 const char *operands = inst->GetOperands(&exe_ctx); 3809 const char *comment = inst->GetComment(&exe_ctx); 3810 3811 if (mnemonic != nullptr && mnemonic[0] == '\0') 3812 mnemonic = nullptr; 3813 if (operands != nullptr && operands[0] == '\0') 3814 operands = nullptr; 3815 if (comment != nullptr && comment[0] == '\0') 3816 comment = nullptr; 3817 3818 strm.Clear(); 3819 3820 if (mnemonic != nullptr && operands != nullptr && comment != nullptr) 3821 strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment); 3822 else if (mnemonic != nullptr && operands != nullptr) 3823 strm.Printf("%-8s %s", mnemonic, operands); 3824 else if (mnemonic != nullptr) 3825 strm.Printf("%s", mnemonic); 3826 3827 int right_pad = 1; 3828 window.PutCStringTruncated( 3829 right_pad, 3830 strm.GetString().substr(m_first_visible_column).data()); 3831 3832 if (is_pc_line && frame_sp && 3833 frame_sp->GetConcreteFrameIndex() == 0) { 3834 StopInfoSP stop_info_sp; 3835 if (thread) 3836 stop_info_sp = thread->GetStopInfo(); 3837 if (stop_info_sp) { 3838 const char *stop_description = stop_info_sp->GetDescription(); 3839 if (stop_description && stop_description[0]) { 3840 size_t stop_description_len = strlen(stop_description); 3841 int desc_x = window_width - stop_description_len - 16; 3842 if (desc_x - window.GetCursorX() > 0) 3843 window.Printf("%*s", desc_x - window.GetCursorX(), ""); 3844 window.MoveCursor(window_width - stop_description_len - 15, 3845 line_y); 3846 window.PrintfTruncated(1, "<<< Thread %u: %s ", 3847 thread->GetIndexID(), stop_description); 3848 } 3849 } else { 3850 window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); 3851 } 3852 } 3853 if (highlight_attr) 3854 window.AttributeOff(highlight_attr); 3855 } 3856 } 3857 } 3858 return true; // Drawing handled 3859 } 3860 3861 size_t GetNumLines() { 3862 size_t num_lines = GetNumSourceLines(); 3863 if (num_lines == 0) 3864 num_lines = GetNumDisassemblyLines(); 3865 return num_lines; 3866 } 3867 3868 size_t GetNumSourceLines() const { 3869 if (m_file_sp) 3870 return m_file_sp->GetNumLines(); 3871 return 0; 3872 } 3873 3874 size_t GetNumDisassemblyLines() const { 3875 if (m_disassembly_sp) 3876 return m_disassembly_sp->GetInstructionList().GetSize(); 3877 return 0; 3878 } 3879 3880 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 3881 const uint32_t num_visible_lines = NumVisibleLines(); 3882 const size_t num_lines = GetNumLines(); 3883 3884 switch (c) { 3885 case ',': 3886 case KEY_PPAGE: 3887 // Page up key 3888 if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines) 3889 m_first_visible_line -= num_visible_lines; 3890 else 3891 m_first_visible_line = 0; 3892 m_selected_line = m_first_visible_line; 3893 return eKeyHandled; 3894 3895 case '.': 3896 case KEY_NPAGE: 3897 // Page down key 3898 { 3899 if (m_first_visible_line + num_visible_lines < num_lines) 3900 m_first_visible_line += num_visible_lines; 3901 else if (num_lines < num_visible_lines) 3902 m_first_visible_line = 0; 3903 else 3904 m_first_visible_line = num_lines - num_visible_lines; 3905 m_selected_line = m_first_visible_line; 3906 } 3907 return eKeyHandled; 3908 3909 case KEY_UP: 3910 if (m_selected_line > 0) { 3911 m_selected_line--; 3912 if (static_cast<size_t>(m_first_visible_line) > m_selected_line) 3913 m_first_visible_line = m_selected_line; 3914 } 3915 return eKeyHandled; 3916 3917 case KEY_DOWN: 3918 if (m_selected_line + 1 < num_lines) { 3919 m_selected_line++; 3920 if (m_first_visible_line + num_visible_lines < m_selected_line) 3921 m_first_visible_line++; 3922 } 3923 return eKeyHandled; 3924 3925 case KEY_LEFT: 3926 if (m_first_visible_column > 0) 3927 --m_first_visible_column; 3928 return eKeyHandled; 3929 3930 case KEY_RIGHT: 3931 ++m_first_visible_column; 3932 return eKeyHandled; 3933 3934 case '\r': 3935 case '\n': 3936 case KEY_ENTER: 3937 // Set a breakpoint and run to the line using a one shot breakpoint 3938 if (GetNumSourceLines() > 0) { 3939 ExecutionContext exe_ctx = 3940 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3941 if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) { 3942 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 3943 nullptr, // Don't limit the breakpoint to certain modules 3944 m_file_sp->GetFileSpec(), // Source file 3945 m_selected_line + 3946 1, // Source line number (m_selected_line is zero based) 3947 0, // Unspecified column. 3948 0, // No offset 3949 eLazyBoolCalculate, // Check inlines using global setting 3950 eLazyBoolCalculate, // Skip prologue using global setting, 3951 false, // internal 3952 false, // request_hardware 3953 eLazyBoolCalculate); // move_to_nearest_code 3954 // Make breakpoint one shot 3955 bp_sp->GetOptions().SetOneShot(true); 3956 exe_ctx.GetProcessRef().Resume(); 3957 } 3958 } else if (m_selected_line < GetNumDisassemblyLines()) { 3959 const Instruction *inst = m_disassembly_sp->GetInstructionList() 3960 .GetInstructionAtIndex(m_selected_line) 3961 .get(); 3962 ExecutionContext exe_ctx = 3963 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3964 if (exe_ctx.HasTargetScope()) { 3965 Address addr = inst->GetAddress(); 3966 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 3967 addr, // lldb_private::Address 3968 false, // internal 3969 false); // request_hardware 3970 // Make breakpoint one shot 3971 bp_sp->GetOptions().SetOneShot(true); 3972 exe_ctx.GetProcessRef().Resume(); 3973 } 3974 } 3975 return eKeyHandled; 3976 3977 case 'b': // 'b' == toggle breakpoint on currently selected line 3978 ToggleBreakpointOnSelectedLine(); 3979 return eKeyHandled; 3980 3981 case 'D': // 'D' == detach and keep stopped 3982 { 3983 ExecutionContext exe_ctx = 3984 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3985 if (exe_ctx.HasProcessScope()) 3986 exe_ctx.GetProcessRef().Detach(true); 3987 } 3988 return eKeyHandled; 3989 3990 case 'c': 3991 // 'c' == continue 3992 { 3993 ExecutionContext exe_ctx = 3994 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3995 if (exe_ctx.HasProcessScope()) 3996 exe_ctx.GetProcessRef().Resume(); 3997 } 3998 return eKeyHandled; 3999 4000 case 'f': 4001 // 'f' == step out (finish) 4002 { 4003 ExecutionContext exe_ctx = 4004 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4005 if (exe_ctx.HasThreadScope() && 4006 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 4007 exe_ctx.GetThreadRef().StepOut(); 4008 } 4009 } 4010 return eKeyHandled; 4011 4012 case 'n': // 'n' == step over 4013 case 'N': // 'N' == step over instruction 4014 { 4015 ExecutionContext exe_ctx = 4016 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4017 if (exe_ctx.HasThreadScope() && 4018 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 4019 bool source_step = (c == 'n'); 4020 exe_ctx.GetThreadRef().StepOver(source_step); 4021 } 4022 } 4023 return eKeyHandled; 4024 4025 case 's': // 's' == step into 4026 case 'S': // 'S' == step into instruction 4027 { 4028 ExecutionContext exe_ctx = 4029 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4030 if (exe_ctx.HasThreadScope() && 4031 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 4032 bool source_step = (c == 's'); 4033 exe_ctx.GetThreadRef().StepIn(source_step); 4034 } 4035 } 4036 return eKeyHandled; 4037 4038 case 'u': // 'u' == frame up 4039 case 'd': // 'd' == frame down 4040 { 4041 ExecutionContext exe_ctx = 4042 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4043 if (exe_ctx.HasThreadScope()) { 4044 Thread *thread = exe_ctx.GetThreadPtr(); 4045 uint32_t frame_idx = thread->GetSelectedFrameIndex(); 4046 if (frame_idx == UINT32_MAX) 4047 frame_idx = 0; 4048 if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount()) 4049 ++frame_idx; 4050 else if (c == 'd' && frame_idx > 0) 4051 --frame_idx; 4052 if (thread->SetSelectedFrameByIndex(frame_idx, true)) 4053 exe_ctx.SetFrameSP(thread->GetSelectedFrame()); 4054 } 4055 } 4056 return eKeyHandled; 4057 4058 case 'h': 4059 window.CreateHelpSubwindow(); 4060 return eKeyHandled; 4061 4062 default: 4063 break; 4064 } 4065 return eKeyNotHandled; 4066 } 4067 4068 void ToggleBreakpointOnSelectedLine() { 4069 ExecutionContext exe_ctx = 4070 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4071 if (!exe_ctx.HasTargetScope()) 4072 return; 4073 if (GetNumSourceLines() > 0) { 4074 // Source file breakpoint. 4075 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); 4076 const size_t num_bps = bp_list.GetSize(); 4077 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 4078 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 4079 const size_t num_bps_locs = bp_sp->GetNumLocations(); 4080 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 4081 BreakpointLocationSP bp_loc_sp = 4082 bp_sp->GetLocationAtIndex(bp_loc_idx); 4083 LineEntry bp_loc_line_entry; 4084 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( 4085 bp_loc_line_entry)) { 4086 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file && 4087 m_selected_line + 1 == bp_loc_line_entry.line) { 4088 bool removed = 4089 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); 4090 assert(removed); 4091 UNUSED_IF_ASSERT_DISABLED(removed); 4092 return; // Existing breakpoint removed. 4093 } 4094 } 4095 } 4096 } 4097 // No breakpoint found on the location, add it. 4098 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 4099 nullptr, // Don't limit the breakpoint to certain modules 4100 m_file_sp->GetFileSpec(), // Source file 4101 m_selected_line + 4102 1, // Source line number (m_selected_line is zero based) 4103 0, // No column specified. 4104 0, // No offset 4105 eLazyBoolCalculate, // Check inlines using global setting 4106 eLazyBoolCalculate, // Skip prologue using global setting, 4107 false, // internal 4108 false, // request_hardware 4109 eLazyBoolCalculate); // move_to_nearest_code 4110 } else { 4111 // Disassembly breakpoint. 4112 assert(GetNumDisassemblyLines() > 0); 4113 assert(m_selected_line < GetNumDisassemblyLines()); 4114 const Instruction *inst = m_disassembly_sp->GetInstructionList() 4115 .GetInstructionAtIndex(m_selected_line) 4116 .get(); 4117 Address addr = inst->GetAddress(); 4118 // Try to find it. 4119 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); 4120 const size_t num_bps = bp_list.GetSize(); 4121 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 4122 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 4123 const size_t num_bps_locs = bp_sp->GetNumLocations(); 4124 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 4125 BreakpointLocationSP bp_loc_sp = 4126 bp_sp->GetLocationAtIndex(bp_loc_idx); 4127 LineEntry bp_loc_line_entry; 4128 const lldb::addr_t file_addr = 4129 bp_loc_sp->GetAddress().GetFileAddress(); 4130 if (file_addr == addr.GetFileAddress()) { 4131 bool removed = 4132 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); 4133 assert(removed); 4134 UNUSED_IF_ASSERT_DISABLED(removed); 4135 return; // Existing breakpoint removed. 4136 } 4137 } 4138 } 4139 // No breakpoint found on the address, add it. 4140 BreakpointSP bp_sp = 4141 exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address 4142 false, // internal 4143 false); // request_hardware 4144 } 4145 } 4146 4147 protected: 4148 typedef std::set<uint32_t> BreakpointLines; 4149 typedef std::set<lldb::addr_t> BreakpointAddrs; 4150 4151 Debugger &m_debugger; 4152 SymbolContext m_sc; 4153 SourceManager::FileSP m_file_sp; 4154 SymbolContextScope *m_disassembly_scope; 4155 lldb::DisassemblerSP m_disassembly_sp; 4156 AddressRange m_disassembly_range; 4157 StreamString m_title; 4158 lldb::user_id_t m_tid; 4159 int m_line_width; 4160 uint32_t m_selected_line; // The selected line 4161 uint32_t m_pc_line; // The line with the PC 4162 uint32_t m_stop_id; 4163 uint32_t m_frame_idx; 4164 int m_first_visible_line; 4165 int m_first_visible_column; 4166 int m_min_x; 4167 int m_min_y; 4168 int m_max_x; 4169 int m_max_y; 4170 }; 4171 4172 DisplayOptions ValueObjectListDelegate::g_options = {true}; 4173 4174 IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger) 4175 : IOHandler(debugger, IOHandler::Type::Curses) {} 4176 4177 void IOHandlerCursesGUI::Activate() { 4178 IOHandler::Activate(); 4179 if (!m_app_ap) { 4180 m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE()); 4181 4182 // This is both a window and a menu delegate 4183 std::shared_ptr<ApplicationDelegate> app_delegate_sp( 4184 new ApplicationDelegate(*m_app_ap, m_debugger)); 4185 4186 MenuDelegateSP app_menu_delegate_sp = 4187 std::static_pointer_cast<MenuDelegate>(app_delegate_sp); 4188 MenuSP lldb_menu_sp( 4189 new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB)); 4190 MenuSP exit_menuitem_sp( 4191 new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit)); 4192 exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit); 4193 lldb_menu_sp->AddSubmenu(MenuSP(new Menu( 4194 "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout))); 4195 lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 4196 lldb_menu_sp->AddSubmenu(exit_menuitem_sp); 4197 4198 MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2), 4199 ApplicationDelegate::eMenuID_Target)); 4200 target_menu_sp->AddSubmenu(MenuSP(new Menu( 4201 "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate))); 4202 target_menu_sp->AddSubmenu(MenuSP(new Menu( 4203 "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete))); 4204 4205 MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3), 4206 ApplicationDelegate::eMenuID_Process)); 4207 process_menu_sp->AddSubmenu(MenuSP(new Menu( 4208 "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach))); 4209 process_menu_sp->AddSubmenu( 4210 MenuSP(new Menu("Detach and resume", nullptr, 'd', 4211 ApplicationDelegate::eMenuID_ProcessDetachResume))); 4212 process_menu_sp->AddSubmenu( 4213 MenuSP(new Menu("Detach suspended", nullptr, 's', 4214 ApplicationDelegate::eMenuID_ProcessDetachSuspended))); 4215 process_menu_sp->AddSubmenu(MenuSP(new Menu( 4216 "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch))); 4217 process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 4218 process_menu_sp->AddSubmenu( 4219 MenuSP(new Menu("Continue", nullptr, 'c', 4220 ApplicationDelegate::eMenuID_ProcessContinue))); 4221 process_menu_sp->AddSubmenu(MenuSP(new Menu( 4222 "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt))); 4223 process_menu_sp->AddSubmenu(MenuSP(new Menu( 4224 "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill))); 4225 4226 MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4), 4227 ApplicationDelegate::eMenuID_Thread)); 4228 thread_menu_sp->AddSubmenu(MenuSP(new Menu( 4229 "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn))); 4230 thread_menu_sp->AddSubmenu( 4231 MenuSP(new Menu("Step Over", nullptr, 'v', 4232 ApplicationDelegate::eMenuID_ThreadStepOver))); 4233 thread_menu_sp->AddSubmenu(MenuSP(new Menu( 4234 "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut))); 4235 4236 MenuSP view_menu_sp( 4237 new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View)); 4238 view_menu_sp->AddSubmenu( 4239 MenuSP(new Menu("Backtrace", nullptr, 'b', 4240 ApplicationDelegate::eMenuID_ViewBacktrace))); 4241 view_menu_sp->AddSubmenu( 4242 MenuSP(new Menu("Registers", nullptr, 'r', 4243 ApplicationDelegate::eMenuID_ViewRegisters))); 4244 view_menu_sp->AddSubmenu(MenuSP(new Menu( 4245 "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource))); 4246 view_menu_sp->AddSubmenu( 4247 MenuSP(new Menu("Variables", nullptr, 'v', 4248 ApplicationDelegate::eMenuID_ViewVariables))); 4249 4250 MenuSP help_menu_sp( 4251 new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help)); 4252 help_menu_sp->AddSubmenu(MenuSP(new Menu( 4253 "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp))); 4254 4255 m_app_ap->Initialize(); 4256 WindowSP &main_window_sp = m_app_ap->GetMainWindow(); 4257 4258 MenuSP menubar_sp(new Menu(Menu::Type::Bar)); 4259 menubar_sp->AddSubmenu(lldb_menu_sp); 4260 menubar_sp->AddSubmenu(target_menu_sp); 4261 menubar_sp->AddSubmenu(process_menu_sp); 4262 menubar_sp->AddSubmenu(thread_menu_sp); 4263 menubar_sp->AddSubmenu(view_menu_sp); 4264 menubar_sp->AddSubmenu(help_menu_sp); 4265 menubar_sp->SetDelegate(app_menu_delegate_sp); 4266 4267 Rect content_bounds = main_window_sp->GetFrame(); 4268 Rect menubar_bounds = content_bounds.MakeMenuBar(); 4269 Rect status_bounds = content_bounds.MakeStatusBar(); 4270 Rect source_bounds; 4271 Rect variables_bounds; 4272 Rect threads_bounds; 4273 Rect source_variables_bounds; 4274 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, 4275 threads_bounds); 4276 source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds, 4277 variables_bounds); 4278 4279 WindowSP menubar_window_sp = 4280 main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false); 4281 // Let the menubar get keys if the active window doesn't handle the keys 4282 // that are typed so it can respond to menubar key presses. 4283 menubar_window_sp->SetCanBeActive( 4284 false); // Don't let the menubar become the active window 4285 menubar_window_sp->SetDelegate(menubar_sp); 4286 4287 WindowSP source_window_sp( 4288 main_window_sp->CreateSubWindow("Source", source_bounds, true)); 4289 WindowSP variables_window_sp( 4290 main_window_sp->CreateSubWindow("Variables", variables_bounds, false)); 4291 WindowSP threads_window_sp( 4292 main_window_sp->CreateSubWindow("Threads", threads_bounds, false)); 4293 WindowSP status_window_sp( 4294 main_window_sp->CreateSubWindow("Status", status_bounds, false)); 4295 status_window_sp->SetCanBeActive( 4296 false); // Don't let the status bar become the active window 4297 main_window_sp->SetDelegate( 4298 std::static_pointer_cast<WindowDelegate>(app_delegate_sp)); 4299 source_window_sp->SetDelegate( 4300 WindowDelegateSP(new SourceFileWindowDelegate(m_debugger))); 4301 variables_window_sp->SetDelegate( 4302 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 4303 TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger)); 4304 threads_window_sp->SetDelegate(WindowDelegateSP( 4305 new TreeWindowDelegate(m_debugger, thread_delegate_sp))); 4306 status_window_sp->SetDelegate( 4307 WindowDelegateSP(new StatusBarWindowDelegate(m_debugger))); 4308 4309 // Show the main help window once the first time the curses GUI is launched 4310 static bool g_showed_help = false; 4311 if (!g_showed_help) { 4312 g_showed_help = true; 4313 main_window_sp->CreateHelpSubwindow(); 4314 } 4315 4316 // All colors with black background. 4317 init_pair(1, COLOR_BLACK, COLOR_BLACK); 4318 init_pair(2, COLOR_RED, COLOR_BLACK); 4319 init_pair(3, COLOR_GREEN, COLOR_BLACK); 4320 init_pair(4, COLOR_YELLOW, COLOR_BLACK); 4321 init_pair(5, COLOR_BLUE, COLOR_BLACK); 4322 init_pair(6, COLOR_MAGENTA, COLOR_BLACK); 4323 init_pair(7, COLOR_CYAN, COLOR_BLACK); 4324 init_pair(8, COLOR_WHITE, COLOR_BLACK); 4325 // All colors with blue background. 4326 init_pair(9, COLOR_BLACK, COLOR_BLUE); 4327 init_pair(10, COLOR_RED, COLOR_BLUE); 4328 init_pair(11, COLOR_GREEN, COLOR_BLUE); 4329 init_pair(12, COLOR_YELLOW, COLOR_BLUE); 4330 init_pair(13, COLOR_BLUE, COLOR_BLUE); 4331 init_pair(14, COLOR_MAGENTA, COLOR_BLUE); 4332 init_pair(15, COLOR_CYAN, COLOR_BLUE); 4333 init_pair(16, COLOR_WHITE, COLOR_BLUE); 4334 // These must match the order in the color indexes enum. 4335 init_pair(17, COLOR_BLACK, COLOR_WHITE); 4336 init_pair(18, COLOR_MAGENTA, COLOR_WHITE); 4337 static_assert(LastColorPairIndex == 18, "Color indexes do not match."); 4338 } 4339 } 4340 4341 void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); } 4342 4343 void IOHandlerCursesGUI::Run() { 4344 m_app_ap->Run(m_debugger); 4345 SetIsDone(true); 4346 } 4347 4348 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default; 4349 4350 void IOHandlerCursesGUI::Cancel() {} 4351 4352 bool IOHandlerCursesGUI::Interrupt() { return false; } 4353 4354 void IOHandlerCursesGUI::GotEOF() {} 4355 4356 void IOHandlerCursesGUI::TerminalSizeChanged() { 4357 m_app_ap->TerminalSizeChanged(); 4358 } 4359 4360 #endif // LLDB_ENABLE_CURSES 4361