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