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