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