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