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