1 //===-- IOHandlerCursesGUI.cpp --------------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "lldb/Core/IOHandlerCursesGUI.h" 10 #include "lldb/Host/Config.h" 11 12 #if LLDB_ENABLE_CURSES 13 #if CURSES_HAVE_NCURSES_CURSES_H 14 #include <ncurses/curses.h> 15 #include <ncurses/panel.h> 16 #else 17 #include <curses.h> 18 #include <panel.h> 19 #endif 20 #endif 21 22 #if defined(__APPLE__) 23 #include <deque> 24 #endif 25 #include <string> 26 27 #include "lldb/Core/Debugger.h" 28 #include "lldb/Core/StreamFile.h" 29 #include "lldb/Core/ValueObjectUpdater.h" 30 #include "lldb/Host/File.h" 31 #include "lldb/Utility/Predicate.h" 32 #include "lldb/Utility/Status.h" 33 #include "lldb/Utility/StreamString.h" 34 #include "lldb/Utility/StringList.h" 35 #include "lldb/lldb-forward.h" 36 37 #include "lldb/Interpreter/CommandCompletions.h" 38 #include "lldb/Interpreter/CommandInterpreter.h" 39 40 #if LLDB_ENABLE_CURSES 41 #include "lldb/Breakpoint/BreakpointLocation.h" 42 #include "lldb/Core/Module.h" 43 #include "lldb/Core/PluginManager.h" 44 #include "lldb/Core/ValueObject.h" 45 #include "lldb/Core/ValueObjectRegister.h" 46 #include "lldb/Symbol/Block.h" 47 #include "lldb/Symbol/Function.h" 48 #include "lldb/Symbol/Symbol.h" 49 #include "lldb/Symbol/VariableList.h" 50 #include "lldb/Target/Process.h" 51 #include "lldb/Target/RegisterContext.h" 52 #include "lldb/Target/StackFrame.h" 53 #include "lldb/Target/StopInfo.h" 54 #include "lldb/Target/Target.h" 55 #include "lldb/Target/Thread.h" 56 #include "lldb/Utility/State.h" 57 #endif 58 59 #include "llvm/ADT/StringRef.h" 60 61 #ifdef _WIN32 62 #include "lldb/Host/windows/windows.h" 63 #endif 64 65 #include <memory> 66 #include <mutex> 67 68 #include <cassert> 69 #include <cctype> 70 #include <cerrno> 71 #include <cstdint> 72 #include <cstdio> 73 #include <cstring> 74 #include <functional> 75 #include <type_traits> 76 77 using namespace lldb; 78 using namespace lldb_private; 79 using llvm::None; 80 using llvm::Optional; 81 using llvm::StringRef; 82 83 // we may want curses to be disabled for some builds for instance, windows 84 #if LLDB_ENABLE_CURSES 85 86 #define KEY_RETURN 10 87 #define KEY_ESCAPE 27 88 89 #define KEY_SHIFT_TAB (KEY_MAX + 1) 90 91 namespace curses { 92 class Menu; 93 class MenuDelegate; 94 class Window; 95 class WindowDelegate; 96 typedef std::shared_ptr<Menu> MenuSP; 97 typedef std::shared_ptr<MenuDelegate> MenuDelegateSP; 98 typedef std::shared_ptr<Window> WindowSP; 99 typedef std::shared_ptr<WindowDelegate> WindowDelegateSP; 100 typedef std::vector<MenuSP> Menus; 101 typedef std::vector<WindowSP> Windows; 102 typedef std::vector<WindowDelegateSP> WindowDelegates; 103 104 #if 0 105 type summary add -s "x=${var.x}, y=${var.y}" curses::Point 106 type summary add -s "w=${var.width}, h=${var.height}" curses::Size 107 type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect 108 #endif 109 110 struct Point { 111 int x; 112 int y; 113 114 Point(int _x = 0, int _y = 0) : x(_x), y(_y) {} 115 116 void Clear() { 117 x = 0; 118 y = 0; 119 } 120 121 Point &operator+=(const Point &rhs) { 122 x += rhs.x; 123 y += rhs.y; 124 return *this; 125 } 126 127 void Dump() { printf("(x=%i, y=%i)\n", x, y); } 128 }; 129 130 bool operator==(const Point &lhs, const Point &rhs) { 131 return lhs.x == rhs.x && lhs.y == rhs.y; 132 } 133 134 bool operator!=(const Point &lhs, const Point &rhs) { 135 return lhs.x != rhs.x || lhs.y != rhs.y; 136 } 137 138 struct Size { 139 int width; 140 int height; 141 Size(int w = 0, int h = 0) : width(w), height(h) {} 142 143 void Clear() { 144 width = 0; 145 height = 0; 146 } 147 148 void Dump() { printf("(w=%i, h=%i)\n", width, height); } 149 }; 150 151 bool operator==(const Size &lhs, const Size &rhs) { 152 return lhs.width == rhs.width && lhs.height == rhs.height; 153 } 154 155 bool operator!=(const Size &lhs, const Size &rhs) { 156 return lhs.width != rhs.width || lhs.height != rhs.height; 157 } 158 159 struct Rect { 160 Point origin; 161 Size size; 162 163 Rect() : origin(), size() {} 164 165 Rect(const Point &p, const Size &s) : origin(p), size(s) {} 166 167 void Clear() { 168 origin.Clear(); 169 size.Clear(); 170 } 171 172 void Dump() { 173 printf("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width, 174 size.height); 175 } 176 177 void Inset(int w, int h) { 178 if (size.width > w * 2) 179 size.width -= w * 2; 180 origin.x += w; 181 182 if (size.height > h * 2) 183 size.height -= h * 2; 184 origin.y += h; 185 } 186 187 // Return a status bar rectangle which is the last line of this rectangle. 188 // This rectangle will be modified to not include the status bar area. 189 Rect MakeStatusBar() { 190 Rect status_bar; 191 if (size.height > 1) { 192 status_bar.origin.x = origin.x; 193 status_bar.origin.y = size.height; 194 status_bar.size.width = size.width; 195 status_bar.size.height = 1; 196 --size.height; 197 } 198 return status_bar; 199 } 200 201 // Return a menubar rectangle which is the first line of this rectangle. This 202 // rectangle will be modified to not include the menubar area. 203 Rect MakeMenuBar() { 204 Rect menubar; 205 if (size.height > 1) { 206 menubar.origin.x = origin.x; 207 menubar.origin.y = origin.y; 208 menubar.size.width = size.width; 209 menubar.size.height = 1; 210 ++origin.y; 211 --size.height; 212 } 213 return menubar; 214 } 215 216 void HorizontalSplitPercentage(float top_percentage, Rect &top, 217 Rect &bottom) const { 218 float top_height = top_percentage * size.height; 219 HorizontalSplit(top_height, top, bottom); 220 } 221 222 void HorizontalSplit(int top_height, Rect &top, Rect &bottom) const { 223 top = *this; 224 if (top_height < size.height) { 225 top.size.height = top_height; 226 bottom.origin.x = origin.x; 227 bottom.origin.y = origin.y + top.size.height; 228 bottom.size.width = size.width; 229 bottom.size.height = size.height - top.size.height; 230 } else { 231 bottom.Clear(); 232 } 233 } 234 235 void VerticalSplitPercentage(float left_percentage, Rect &left, 236 Rect &right) const { 237 float left_width = left_percentage * size.width; 238 VerticalSplit(left_width, left, right); 239 } 240 241 void VerticalSplit(int left_width, Rect &left, Rect &right) const { 242 left = *this; 243 if (left_width < size.width) { 244 left.size.width = left_width; 245 right.origin.x = origin.x + left.size.width; 246 right.origin.y = origin.y; 247 right.size.width = size.width - left.size.width; 248 right.size.height = size.height; 249 } else { 250 right.Clear(); 251 } 252 } 253 }; 254 255 bool operator==(const Rect &lhs, const Rect &rhs) { 256 return lhs.origin == rhs.origin && lhs.size == rhs.size; 257 } 258 259 bool operator!=(const Rect &lhs, const Rect &rhs) { 260 return lhs.origin != rhs.origin || lhs.size != rhs.size; 261 } 262 263 enum HandleCharResult { 264 eKeyNotHandled = 0, 265 eKeyHandled = 1, 266 eQuitApplication = 2 267 }; 268 269 enum class MenuActionResult { 270 Handled, 271 NotHandled, 272 Quit // Exit all menus and quit 273 }; 274 275 struct KeyHelp { 276 int ch; 277 const char *description; 278 }; 279 280 // COLOR_PAIR index names 281 enum { 282 // First 16 colors are 8 black background and 8 blue background colors, 283 // needed by OutputColoredStringTruncated(). 284 BlackOnBlack = 1, 285 RedOnBlack, 286 GreenOnBlack, 287 YellowOnBlack, 288 BlueOnBlack, 289 MagentaOnBlack, 290 CyanOnBlack, 291 WhiteOnBlack, 292 BlackOnBlue, 293 RedOnBlue, 294 GreenOnBlue, 295 YellowOnBlue, 296 BlueOnBlue, 297 MagentaOnBlue, 298 CyanOnBlue, 299 WhiteOnBlue, 300 // Other colors, as needed. 301 BlackOnWhite, 302 MagentaOnWhite, 303 LastColorPairIndex = MagentaOnWhite 304 }; 305 306 class WindowDelegate { 307 public: 308 virtual ~WindowDelegate() = default; 309 310 virtual bool WindowDelegateDraw(Window &window, bool force) { 311 return false; // Drawing not handled 312 } 313 314 virtual HandleCharResult WindowDelegateHandleChar(Window &window, int key) { 315 return eKeyNotHandled; 316 } 317 318 virtual const char *WindowDelegateGetHelpText() { return nullptr; } 319 320 virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; } 321 }; 322 323 class HelpDialogDelegate : public WindowDelegate { 324 public: 325 HelpDialogDelegate(const char *text, KeyHelp *key_help_array); 326 327 ~HelpDialogDelegate() override; 328 329 bool WindowDelegateDraw(Window &window, bool force) override; 330 331 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override; 332 333 size_t GetNumLines() const { return m_text.GetSize(); } 334 335 size_t GetMaxLineLength() const { return m_text.GetMaxStringLength(); } 336 337 protected: 338 StringList m_text; 339 int m_first_visible_line; 340 }; 341 342 // A surface is an abstraction for something than can be drawn on. The surface 343 // have a width, a height, a cursor position, and a multitude of drawing 344 // operations. This type should be sub-classed to get an actually useful ncurses 345 // object, such as a Window, SubWindow, Pad, or a SubPad. 346 class Surface { 347 public: 348 Surface() : m_window(nullptr) {} 349 350 WINDOW *get() { return m_window; } 351 352 operator WINDOW *() { return m_window; } 353 354 // Copy a region of the surface to another surface. 355 void CopyToSurface(Surface &target, Point source_origin, Point target_origin, 356 Size size) { 357 ::copywin(m_window, target.get(), source_origin.y, source_origin.x, 358 target_origin.y, target_origin.x, 359 target_origin.y + size.height - 1, 360 target_origin.x + size.width - 1, false); 361 } 362 363 int GetCursorX() const { return getcurx(m_window); } 364 int GetCursorY() const { return getcury(m_window); } 365 void MoveCursor(int x, int y) { ::wmove(m_window, y, x); } 366 367 void AttributeOn(attr_t attr) { ::wattron(m_window, attr); } 368 void AttributeOff(attr_t attr) { ::wattroff(m_window, attr); } 369 370 int GetMaxX() const { return getmaxx(m_window); } 371 int GetMaxY() const { return getmaxy(m_window); } 372 int GetWidth() const { return GetMaxX(); } 373 int GetHeight() const { return GetMaxY(); } 374 Size GetSize() const { return Size(GetWidth(), GetHeight()); } 375 // Get a zero origin rectangle width the surface size. 376 Rect GetFrame() const { return Rect(Point(), GetSize()); } 377 378 void Clear() { ::wclear(m_window); } 379 void Erase() { ::werase(m_window); } 380 381 void SetBackground(int color_pair_idx) { 382 ::wbkgd(m_window, COLOR_PAIR(color_pair_idx)); 383 } 384 385 void PutChar(int ch) { ::waddch(m_window, ch); } 386 void PutCString(const char *s, int len = -1) { ::waddnstr(m_window, s, len); } 387 388 void PutCStringTruncated(int right_pad, const char *s, int len = -1) { 389 int bytes_left = GetWidth() - GetCursorX(); 390 if (bytes_left > right_pad) { 391 bytes_left -= right_pad; 392 ::waddnstr(m_window, s, len < 0 ? bytes_left : std::min(bytes_left, len)); 393 } 394 } 395 396 void Printf(const char *format, ...) __attribute__((format(printf, 2, 3))) { 397 va_list args; 398 va_start(args, format); 399 vw_printw(m_window, format, args); 400 va_end(args); 401 } 402 403 void PrintfTruncated(int right_pad, const char *format, ...) 404 __attribute__((format(printf, 3, 4))) { 405 va_list args; 406 va_start(args, format); 407 StreamString strm; 408 strm.PrintfVarArg(format, args); 409 va_end(args); 410 PutCStringTruncated(right_pad, strm.GetData()); 411 } 412 413 void VerticalLine(int n, chtype v_char = ACS_VLINE) { 414 ::wvline(m_window, v_char, n); 415 } 416 void HorizontalLine(int n, chtype h_char = ACS_HLINE) { 417 ::whline(m_window, h_char, n); 418 } 419 void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) { 420 ::box(m_window, v_char, h_char); 421 } 422 423 void TitledBox(const char *title, chtype v_char = ACS_VLINE, 424 chtype h_char = ACS_HLINE) { 425 Box(v_char, h_char); 426 int title_offset = 2; 427 MoveCursor(title_offset, 0); 428 PutChar('['); 429 PutCString(title, GetWidth() - title_offset); 430 PutChar(']'); 431 } 432 433 void Box(const Rect &bounds, chtype v_char = ACS_VLINE, 434 chtype h_char = ACS_HLINE) { 435 MoveCursor(bounds.origin.x, bounds.origin.y); 436 VerticalLine(bounds.size.height); 437 HorizontalLine(bounds.size.width); 438 PutChar(ACS_ULCORNER); 439 440 MoveCursor(bounds.origin.x + bounds.size.width - 1, bounds.origin.y); 441 VerticalLine(bounds.size.height); 442 PutChar(ACS_URCORNER); 443 444 MoveCursor(bounds.origin.x, bounds.origin.y + bounds.size.height - 1); 445 HorizontalLine(bounds.size.width); 446 PutChar(ACS_LLCORNER); 447 448 MoveCursor(bounds.origin.x + bounds.size.width - 1, 449 bounds.origin.y + bounds.size.height - 1); 450 PutChar(ACS_LRCORNER); 451 } 452 453 void TitledBox(const Rect &bounds, const char *title, 454 chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) { 455 Box(bounds, v_char, h_char); 456 int title_offset = 2; 457 MoveCursor(bounds.origin.x + title_offset, bounds.origin.y); 458 PutChar('['); 459 PutCString(title, bounds.size.width - title_offset); 460 PutChar(']'); 461 } 462 463 // Curses doesn't allow direct output of color escape sequences, but that's 464 // how we get source lines from the Highligher class. Read the line and 465 // convert color escape sequences to curses color attributes. Use 466 // first_skip_count to skip leading visible characters. Returns false if all 467 // visible characters were skipped due to first_skip_count. 468 bool OutputColoredStringTruncated(int right_pad, StringRef string, 469 size_t skip_first_count, 470 bool use_blue_background) { 471 attr_t saved_attr; 472 short saved_pair; 473 bool result = false; 474 wattr_get(m_window, &saved_attr, &saved_pair, nullptr); 475 if (use_blue_background) 476 ::wattron(m_window, COLOR_PAIR(WhiteOnBlue)); 477 while (!string.empty()) { 478 size_t esc_pos = string.find('\x1b'); 479 if (esc_pos == StringRef::npos) { 480 string = string.substr(skip_first_count); 481 if (!string.empty()) { 482 PutCStringTruncated(right_pad, string.data(), string.size()); 483 result = true; 484 } 485 break; 486 } 487 if (esc_pos > 0) { 488 if (skip_first_count > 0) { 489 int skip = std::min(esc_pos, skip_first_count); 490 string = string.substr(skip); 491 skip_first_count -= skip; 492 esc_pos -= skip; 493 } 494 if (esc_pos > 0) { 495 PutCStringTruncated(right_pad, string.data(), esc_pos); 496 result = true; 497 string = string.drop_front(esc_pos); 498 } 499 } 500 bool consumed = string.consume_front("\x1b"); 501 assert(consumed); 502 UNUSED_IF_ASSERT_DISABLED(consumed); 503 // This is written to match our Highlighter classes, which seem to 504 // generate only foreground color escape sequences. If necessary, this 505 // will need to be extended. 506 if (!string.consume_front("[")) { 507 llvm::errs() << "Missing '[' in color escape sequence.\n"; 508 continue; 509 } 510 // Only 8 basic foreground colors and reset, our Highlighter doesn't use 511 // anything else. 512 int value; 513 if (!!string.consumeInteger(10, value) || // Returns false on success. 514 !(value == 0 || (value >= 30 && value <= 37))) { 515 llvm::errs() << "No valid color code in color escape sequence.\n"; 516 continue; 517 } 518 if (!string.consume_front("m")) { 519 llvm::errs() << "Missing 'm' in color escape sequence.\n"; 520 continue; 521 } 522 if (value == 0) { // Reset. 523 wattr_set(m_window, saved_attr, saved_pair, nullptr); 524 if (use_blue_background) 525 ::wattron(m_window, COLOR_PAIR(WhiteOnBlue)); 526 } else { 527 // Mapped directly to first 16 color pairs (black/blue background). 528 ::wattron(m_window, 529 COLOR_PAIR(value - 30 + 1 + (use_blue_background ? 8 : 0))); 530 } 531 } 532 wattr_set(m_window, saved_attr, saved_pair, nullptr); 533 return result; 534 } 535 536 protected: 537 WINDOW *m_window; 538 }; 539 540 class Pad : public Surface { 541 public: 542 Pad(Size size) { m_window = ::newpad(size.height, size.width); } 543 544 ~Pad() { ::delwin(m_window); } 545 }; 546 547 class SubPad : public Surface { 548 public: 549 SubPad(Pad &pad, Rect bounds) { 550 m_window = ::subpad(pad.get(), bounds.size.height, bounds.size.width, 551 bounds.origin.y, bounds.origin.x); 552 } 553 SubPad(SubPad &subpad, Rect bounds) { 554 m_window = ::subpad(subpad.get(), bounds.size.height, bounds.size.width, 555 bounds.origin.y, bounds.origin.x); 556 } 557 558 ~SubPad() { ::delwin(m_window); } 559 }; 560 561 class Window : public Surface { 562 public: 563 Window(const char *name) 564 : m_name(name), m_panel(nullptr), m_parent(nullptr), m_subwindows(), 565 m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX), 566 m_prev_active_window_idx(UINT32_MAX), m_delete(false), 567 m_needs_update(true), m_can_activate(true), m_is_subwin(false) {} 568 569 Window(const char *name, WINDOW *w, bool del = true) 570 : m_name(name), m_panel(nullptr), m_parent(nullptr), m_subwindows(), 571 m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX), 572 m_prev_active_window_idx(UINT32_MAX), m_delete(del), 573 m_needs_update(true), m_can_activate(true), m_is_subwin(false) { 574 if (w) 575 Reset(w); 576 } 577 578 Window(const char *name, const Rect &bounds) 579 : m_name(name), m_parent(nullptr), m_subwindows(), m_delegate_sp(), 580 m_curr_active_window_idx(UINT32_MAX), 581 m_prev_active_window_idx(UINT32_MAX), m_delete(true), 582 m_needs_update(true), m_can_activate(true), m_is_subwin(false) { 583 Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y, 584 bounds.origin.y)); 585 } 586 587 virtual ~Window() { 588 RemoveSubWindows(); 589 Reset(); 590 } 591 592 void Reset(WINDOW *w = nullptr, bool del = true) { 593 if (m_window == w) 594 return; 595 596 if (m_panel) { 597 ::del_panel(m_panel); 598 m_panel = nullptr; 599 } 600 if (m_window && m_delete) { 601 ::delwin(m_window); 602 m_window = nullptr; 603 m_delete = false; 604 } 605 if (w) { 606 m_window = w; 607 m_panel = ::new_panel(m_window); 608 m_delete = del; 609 } 610 } 611 612 // Get the rectangle in our parent window 613 Rect GetBounds() const { return Rect(GetParentOrigin(), GetSize()); } 614 615 Rect GetCenteredRect(int width, int height) { 616 Size size = GetSize(); 617 width = std::min(size.width, width); 618 height = std::min(size.height, height); 619 int x = (size.width - width) / 2; 620 int y = (size.height - height) / 2; 621 return Rect(Point(x, y), Size(width, height)); 622 } 623 624 int GetChar() { return ::wgetch(m_window); } 625 Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); } 626 int GetParentX() const { return getparx(m_window); } 627 int GetParentY() const { return getpary(m_window); } 628 void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); } 629 void Resize(int w, int h) { ::wresize(m_window, h, w); } 630 void Resize(const Size &size) { 631 ::wresize(m_window, size.height, size.width); 632 } 633 void MoveWindow(const Point &origin) { 634 const bool moving_window = origin != GetParentOrigin(); 635 if (m_is_subwin && moving_window) { 636 // Can't move subwindows, must delete and re-create 637 Size size = GetSize(); 638 Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y, 639 origin.x), 640 true); 641 } else { 642 ::mvwin(m_window, origin.y, origin.x); 643 } 644 } 645 646 void SetBounds(const Rect &bounds) { 647 const bool moving_window = bounds.origin != GetParentOrigin(); 648 if (m_is_subwin && moving_window) { 649 // Can't move subwindows, must delete and re-create 650 Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width, 651 bounds.origin.y, bounds.origin.x), 652 true); 653 } else { 654 if (moving_window) 655 MoveWindow(bounds.origin); 656 Resize(bounds.size); 657 } 658 } 659 660 void Touch() { 661 ::touchwin(m_window); 662 if (m_parent) 663 m_parent->Touch(); 664 } 665 666 WindowSP CreateSubWindow(const char *name, const Rect &bounds, 667 bool make_active) { 668 auto get_window = [this, &bounds]() { 669 return m_window 670 ? ::subwin(m_window, bounds.size.height, bounds.size.width, 671 bounds.origin.y, bounds.origin.x) 672 : ::newwin(bounds.size.height, bounds.size.width, 673 bounds.origin.y, bounds.origin.x); 674 }; 675 WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true); 676 subwindow_sp->m_is_subwin = subwindow_sp.operator bool(); 677 subwindow_sp->m_parent = this; 678 if (make_active) { 679 m_prev_active_window_idx = m_curr_active_window_idx; 680 m_curr_active_window_idx = m_subwindows.size(); 681 } 682 m_subwindows.push_back(subwindow_sp); 683 ::top_panel(subwindow_sp->m_panel); 684 m_needs_update = true; 685 return subwindow_sp; 686 } 687 688 bool RemoveSubWindow(Window *window) { 689 Windows::iterator pos, end = m_subwindows.end(); 690 size_t i = 0; 691 for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) { 692 if ((*pos).get() == window) { 693 if (m_prev_active_window_idx == i) 694 m_prev_active_window_idx = UINT32_MAX; 695 else if (m_prev_active_window_idx != UINT32_MAX && 696 m_prev_active_window_idx > i) 697 --m_prev_active_window_idx; 698 699 if (m_curr_active_window_idx == i) 700 m_curr_active_window_idx = UINT32_MAX; 701 else if (m_curr_active_window_idx != UINT32_MAX && 702 m_curr_active_window_idx > i) 703 --m_curr_active_window_idx; 704 window->Erase(); 705 m_subwindows.erase(pos); 706 m_needs_update = true; 707 if (m_parent) 708 m_parent->Touch(); 709 else 710 ::touchwin(stdscr); 711 return true; 712 } 713 } 714 return false; 715 } 716 717 WindowSP FindSubWindow(const char *name) { 718 Windows::iterator pos, end = m_subwindows.end(); 719 size_t i = 0; 720 for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) { 721 if ((*pos)->m_name == name) 722 return *pos; 723 } 724 return WindowSP(); 725 } 726 727 void RemoveSubWindows() { 728 m_curr_active_window_idx = UINT32_MAX; 729 m_prev_active_window_idx = UINT32_MAX; 730 for (Windows::iterator pos = m_subwindows.begin(); 731 pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) { 732 (*pos)->Erase(); 733 } 734 if (m_parent) 735 m_parent->Touch(); 736 else 737 ::touchwin(stdscr); 738 } 739 740 // Window drawing utilities 741 void DrawTitleBox(const char *title, const char *bottom_message = nullptr) { 742 attr_t attr = 0; 743 if (IsActive()) 744 attr = A_BOLD | COLOR_PAIR(BlackOnWhite); 745 else 746 attr = 0; 747 if (attr) 748 AttributeOn(attr); 749 750 Box(); 751 MoveCursor(3, 0); 752 753 if (title && title[0]) { 754 PutChar('<'); 755 PutCString(title); 756 PutChar('>'); 757 } 758 759 if (bottom_message && bottom_message[0]) { 760 int bottom_message_length = strlen(bottom_message); 761 int x = GetWidth() - 3 - (bottom_message_length + 2); 762 763 if (x > 0) { 764 MoveCursor(x, GetHeight() - 1); 765 PutChar('['); 766 PutCString(bottom_message); 767 PutChar(']'); 768 } else { 769 MoveCursor(1, GetHeight() - 1); 770 PutChar('['); 771 PutCStringTruncated(1, bottom_message); 772 } 773 } 774 if (attr) 775 AttributeOff(attr); 776 } 777 778 virtual void Draw(bool force) { 779 if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force)) 780 return; 781 782 for (auto &subwindow_sp : m_subwindows) 783 subwindow_sp->Draw(force); 784 } 785 786 bool CreateHelpSubwindow() { 787 if (m_delegate_sp) { 788 const char *text = m_delegate_sp->WindowDelegateGetHelpText(); 789 KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp(); 790 if ((text && text[0]) || key_help) { 791 std::unique_ptr<HelpDialogDelegate> help_delegate_up( 792 new HelpDialogDelegate(text, key_help)); 793 const size_t num_lines = help_delegate_up->GetNumLines(); 794 const size_t max_length = help_delegate_up->GetMaxLineLength(); 795 Rect bounds = GetBounds(); 796 bounds.Inset(1, 1); 797 if (max_length + 4 < static_cast<size_t>(bounds.size.width)) { 798 bounds.origin.x += (bounds.size.width - max_length + 4) / 2; 799 bounds.size.width = max_length + 4; 800 } else { 801 if (bounds.size.width > 100) { 802 const int inset_w = bounds.size.width / 4; 803 bounds.origin.x += inset_w; 804 bounds.size.width -= 2 * inset_w; 805 } 806 } 807 808 if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) { 809 bounds.origin.y += (bounds.size.height - num_lines + 2) / 2; 810 bounds.size.height = num_lines + 2; 811 } else { 812 if (bounds.size.height > 100) { 813 const int inset_h = bounds.size.height / 4; 814 bounds.origin.y += inset_h; 815 bounds.size.height -= 2 * inset_h; 816 } 817 } 818 WindowSP help_window_sp; 819 Window *parent_window = GetParent(); 820 if (parent_window) 821 help_window_sp = parent_window->CreateSubWindow("Help", bounds, true); 822 else 823 help_window_sp = CreateSubWindow("Help", bounds, true); 824 help_window_sp->SetDelegate( 825 WindowDelegateSP(help_delegate_up.release())); 826 return true; 827 } 828 } 829 return false; 830 } 831 832 virtual HandleCharResult HandleChar(int key) { 833 // Always check the active window first 834 HandleCharResult result = eKeyNotHandled; 835 WindowSP active_window_sp = GetActiveWindow(); 836 if (active_window_sp) { 837 result = active_window_sp->HandleChar(key); 838 if (result != eKeyNotHandled) 839 return result; 840 } 841 842 if (m_delegate_sp) { 843 result = m_delegate_sp->WindowDelegateHandleChar(*this, key); 844 if (result != eKeyNotHandled) 845 return result; 846 } 847 848 // Then check for any windows that want any keys that weren't handled. This 849 // is typically only for a menubar. Make a copy of the subwindows in case 850 // any HandleChar() functions muck with the subwindows. If we don't do 851 // this, we can crash when iterating over the subwindows. 852 Windows subwindows(m_subwindows); 853 for (auto subwindow_sp : subwindows) { 854 if (!subwindow_sp->m_can_activate) { 855 HandleCharResult result = subwindow_sp->HandleChar(key); 856 if (result != eKeyNotHandled) 857 return result; 858 } 859 } 860 861 return eKeyNotHandled; 862 } 863 864 WindowSP GetActiveWindow() { 865 if (!m_subwindows.empty()) { 866 if (m_curr_active_window_idx >= m_subwindows.size()) { 867 if (m_prev_active_window_idx < m_subwindows.size()) { 868 m_curr_active_window_idx = m_prev_active_window_idx; 869 m_prev_active_window_idx = UINT32_MAX; 870 } else if (IsActive()) { 871 m_prev_active_window_idx = UINT32_MAX; 872 m_curr_active_window_idx = UINT32_MAX; 873 874 // Find first window that wants to be active if this window is active 875 const size_t num_subwindows = m_subwindows.size(); 876 for (size_t i = 0; i < num_subwindows; ++i) { 877 if (m_subwindows[i]->GetCanBeActive()) { 878 m_curr_active_window_idx = i; 879 break; 880 } 881 } 882 } 883 } 884 885 if (m_curr_active_window_idx < m_subwindows.size()) 886 return m_subwindows[m_curr_active_window_idx]; 887 } 888 return WindowSP(); 889 } 890 891 bool GetCanBeActive() const { return m_can_activate; } 892 893 void SetCanBeActive(bool b) { m_can_activate = b; } 894 895 void SetDelegate(const WindowDelegateSP &delegate_sp) { 896 m_delegate_sp = delegate_sp; 897 } 898 899 Window *GetParent() const { return m_parent; } 900 901 bool IsActive() const { 902 if (m_parent) 903 return m_parent->GetActiveWindow().get() == this; 904 else 905 return true; // Top level window is always active 906 } 907 908 void SelectNextWindowAsActive() { 909 // Move active focus to next window 910 const int num_subwindows = m_subwindows.size(); 911 int start_idx = 0; 912 if (m_curr_active_window_idx != UINT32_MAX) { 913 m_prev_active_window_idx = m_curr_active_window_idx; 914 start_idx = m_curr_active_window_idx + 1; 915 } 916 for (int idx = start_idx; idx < num_subwindows; ++idx) { 917 if (m_subwindows[idx]->GetCanBeActive()) { 918 m_curr_active_window_idx = idx; 919 return; 920 } 921 } 922 for (int idx = 0; idx < start_idx; ++idx) { 923 if (m_subwindows[idx]->GetCanBeActive()) { 924 m_curr_active_window_idx = idx; 925 break; 926 } 927 } 928 } 929 930 void SelectPreviousWindowAsActive() { 931 // Move active focus to previous window 932 const int num_subwindows = m_subwindows.size(); 933 int start_idx = num_subwindows - 1; 934 if (m_curr_active_window_idx != UINT32_MAX) { 935 m_prev_active_window_idx = m_curr_active_window_idx; 936 start_idx = m_curr_active_window_idx - 1; 937 } 938 for (int idx = start_idx; idx >= 0; --idx) { 939 if (m_subwindows[idx]->GetCanBeActive()) { 940 m_curr_active_window_idx = idx; 941 return; 942 } 943 } 944 for (int idx = num_subwindows - 1; idx > start_idx; --idx) { 945 if (m_subwindows[idx]->GetCanBeActive()) { 946 m_curr_active_window_idx = idx; 947 break; 948 } 949 } 950 } 951 952 const char *GetName() const { return m_name.c_str(); } 953 954 protected: 955 std::string m_name; 956 PANEL *m_panel; 957 Window *m_parent; 958 Windows m_subwindows; 959 WindowDelegateSP m_delegate_sp; 960 uint32_t m_curr_active_window_idx; 961 uint32_t m_prev_active_window_idx; 962 bool m_delete; 963 bool m_needs_update; 964 bool m_can_activate; 965 bool m_is_subwin; 966 967 private: 968 Window(const Window &) = delete; 969 const Window &operator=(const Window &) = delete; 970 }; 971 972 class DerivedWindow : public Surface { 973 public: 974 DerivedWindow(Window &window, Rect bounds) { 975 m_window = ::derwin(window.get(), bounds.size.height, bounds.size.width, 976 bounds.origin.y, bounds.origin.x); 977 } 978 DerivedWindow(DerivedWindow &derived_window, Rect bounds) { 979 m_window = ::derwin(derived_window.get(), bounds.size.height, 980 bounds.size.width, bounds.origin.y, bounds.origin.x); 981 } 982 983 ~DerivedWindow() { ::delwin(m_window); } 984 }; 985 986 ///////// 987 // Forms 988 ///////// 989 990 // A scroll context defines a vertical region that needs to be visible in a 991 // scrolling area. The region is defined by the index of the start and end lines 992 // of the region. The start and end lines may be equal, in which case, the 993 // region is a single line. 994 struct ScrollContext { 995 int start; 996 int end; 997 998 ScrollContext(int line) : start(line), end(line) {} 999 ScrollContext(int _start, int _end) : start(_start), end(_end) {} 1000 1001 void Offset(int offset) { 1002 start += offset; 1003 end += offset; 1004 } 1005 }; 1006 1007 class FieldDelegate { 1008 public: 1009 virtual ~FieldDelegate() = default; 1010 1011 // Returns the number of lines needed to draw the field. The draw method will 1012 // be given a surface that have exactly this number of lines. 1013 virtual int FieldDelegateGetHeight() = 0; 1014 1015 // Returns the scroll context in the local coordinates of the field. By 1016 // default, the scroll context spans the whole field. Bigger fields with 1017 // internal navigation should override this method to provide a finer context. 1018 // Typical override methods would first get the scroll context of the internal 1019 // element then add the offset of the element in the field. 1020 virtual ScrollContext FieldDelegateGetScrollContext() { 1021 return ScrollContext(0, FieldDelegateGetHeight() - 1); 1022 } 1023 1024 // Draw the field in the given subpad surface. The surface have a height that 1025 // is equal to the height returned by FieldDelegateGetHeight(). If the field 1026 // is selected in the form window, then is_selected will be true. 1027 virtual void FieldDelegateDraw(SubPad &surface, bool is_selected) = 0; 1028 1029 // Handle the key that wasn't handled by the form window or a container field. 1030 virtual HandleCharResult FieldDelegateHandleChar(int key) { 1031 return eKeyNotHandled; 1032 } 1033 1034 // This is executed once the user exists the field, that is, once the user 1035 // navigates to the next or the previous field. This is particularly useful to 1036 // do in-field validation and error setting. Fields with internal navigation 1037 // should call this method on their fields. 1038 virtual void FieldDelegateExitCallback() { return; } 1039 1040 // Fields may have internal navigation, for instance, a List Field have 1041 // multiple internal elements, which needs to be navigated. To allow for this 1042 // mechanism, the window shouldn't handle the navigation keys all the time, 1043 // and instead call the key handing method of the selected field. It should 1044 // only handle the navigation keys when the field contains a single element or 1045 // have the last or first element selected depending on if the user is 1046 // navigating forward or backward. Additionally, once a field is selected in 1047 // the forward or backward direction, its first or last internal element 1048 // should be selected. The following methods implements those mechanisms. 1049 1050 // Returns true if the first element in the field is selected or if the field 1051 // contains a single element. 1052 virtual bool FieldDelegateOnFirstOrOnlyElement() { return true; } 1053 1054 // Returns true if the last element in the field is selected or if the field 1055 // contains a single element. 1056 virtual bool FieldDelegateOnLastOrOnlyElement() { return true; } 1057 1058 // Select the first element in the field if multiple elements exists. 1059 virtual void FieldDelegateSelectFirstElement() { return; } 1060 1061 // Select the last element in the field if multiple elements exists. 1062 virtual void FieldDelegateSelectLastElement() { return; } 1063 1064 // Returns true if the field has an error, false otherwise. 1065 virtual bool FieldDelegateHasError() { return false; } 1066 1067 bool FieldDelegateIsVisible() { return m_is_visible; } 1068 1069 void FieldDelegateHide() { m_is_visible = false; } 1070 1071 void FieldDelegateShow() { m_is_visible = true; } 1072 1073 protected: 1074 bool m_is_visible = true; 1075 }; 1076 1077 typedef std::unique_ptr<FieldDelegate> FieldDelegateUP; 1078 1079 class TextFieldDelegate : public FieldDelegate { 1080 public: 1081 TextFieldDelegate(const char *label, const char *content, bool required) 1082 : m_label(label), m_required(required), m_cursor_position(0), 1083 m_first_visibile_char(0) { 1084 if (content) 1085 m_content = content; 1086 } 1087 1088 // Text fields are drawn as titled boxes of a single line, with a possible 1089 // error messages at the end. 1090 // 1091 // __[Label]___________ 1092 // | | 1093 // |__________________| 1094 // - Error message if it exists. 1095 1096 // The text field has a height of 3 lines. 2 lines for borders and 1 line for 1097 // the content. 1098 int GetFieldHeight() { return 3; } 1099 1100 // The text field has a full height of 3 or 4 lines. 3 lines for the actual 1101 // field and an optional line for an error if it exists. 1102 int FieldDelegateGetHeight() override { 1103 int height = GetFieldHeight(); 1104 if (FieldDelegateHasError()) 1105 height++; 1106 return height; 1107 } 1108 1109 // Get the cursor X position in the surface coordinate. 1110 int GetCursorXPosition() { return m_cursor_position - m_first_visibile_char; } 1111 1112 int GetContentLength() { return m_content.length(); } 1113 1114 void DrawContent(SubPad &surface, bool is_selected) { 1115 surface.MoveCursor(0, 0); 1116 const char *text = m_content.c_str() + m_first_visibile_char; 1117 surface.PutCString(text, surface.GetWidth()); 1118 m_last_drawn_content_width = surface.GetWidth(); 1119 1120 // Highlight the cursor. 1121 surface.MoveCursor(GetCursorXPosition(), 0); 1122 if (is_selected) 1123 surface.AttributeOn(A_REVERSE); 1124 if (m_cursor_position == GetContentLength()) 1125 // Cursor is past the last character. Highlight an empty space. 1126 surface.PutChar(' '); 1127 else 1128 surface.PutChar(m_content[m_cursor_position]); 1129 if (is_selected) 1130 surface.AttributeOff(A_REVERSE); 1131 } 1132 1133 void DrawField(SubPad &surface, bool is_selected) { 1134 surface.TitledBox(m_label.c_str()); 1135 1136 Rect content_bounds = surface.GetFrame(); 1137 content_bounds.Inset(1, 1); 1138 SubPad content_surface = SubPad(surface, content_bounds); 1139 1140 DrawContent(content_surface, is_selected); 1141 } 1142 1143 void DrawError(SubPad &surface) { 1144 if (!FieldDelegateHasError()) 1145 return; 1146 surface.MoveCursor(0, 0); 1147 surface.AttributeOn(COLOR_PAIR(RedOnBlack)); 1148 surface.PutChar(ACS_DIAMOND); 1149 surface.PutChar(' '); 1150 surface.PutCStringTruncated(1, GetError().c_str()); 1151 surface.AttributeOff(COLOR_PAIR(RedOnBlack)); 1152 } 1153 1154 void FieldDelegateDraw(SubPad &surface, bool is_selected) override { 1155 Rect frame = surface.GetFrame(); 1156 Rect field_bounds, error_bounds; 1157 frame.HorizontalSplit(GetFieldHeight(), field_bounds, error_bounds); 1158 SubPad field_surface = SubPad(surface, field_bounds); 1159 SubPad error_surface = SubPad(surface, error_bounds); 1160 1161 DrawField(field_surface, is_selected); 1162 DrawError(error_surface); 1163 } 1164 1165 // The cursor is allowed to move one character past the string. 1166 // m_cursor_position is in range [0, GetContentLength()]. 1167 void MoveCursorRight() { 1168 if (m_cursor_position < GetContentLength()) 1169 m_cursor_position++; 1170 } 1171 1172 void MoveCursorLeft() { 1173 if (m_cursor_position > 0) 1174 m_cursor_position--; 1175 } 1176 1177 // If the cursor moved past the last visible character, scroll right by one 1178 // character. 1179 void ScrollRightIfNeeded() { 1180 if (m_cursor_position - m_first_visibile_char == m_last_drawn_content_width) 1181 m_first_visibile_char++; 1182 } 1183 1184 void ScrollLeft() { 1185 if (m_first_visibile_char > 0) 1186 m_first_visibile_char--; 1187 } 1188 1189 // If the cursor moved past the first visible character, scroll left by one 1190 // character. 1191 void ScrollLeftIfNeeded() { 1192 if (m_cursor_position < m_first_visibile_char) 1193 m_first_visibile_char--; 1194 } 1195 1196 // Insert a character at the current cursor position, advance the cursor 1197 // position, and make sure to scroll right if needed. 1198 void InsertChar(char character) { 1199 m_content.insert(m_cursor_position, 1, character); 1200 m_cursor_position++; 1201 ScrollRightIfNeeded(); 1202 } 1203 1204 // Remove the character before the cursor position, retreat the cursor 1205 // position, and make sure to scroll left if needed. 1206 void RemoveChar() { 1207 if (m_cursor_position == 0) 1208 return; 1209 1210 m_content.erase(m_cursor_position - 1, 1); 1211 m_cursor_position--; 1212 ScrollLeft(); 1213 } 1214 1215 // True if the key represents a char that can be inserted in the field 1216 // content, false otherwise. 1217 virtual bool IsAcceptableChar(int key) { return isprint(key); } 1218 1219 HandleCharResult FieldDelegateHandleChar(int key) override { 1220 if (IsAcceptableChar(key)) { 1221 ClearError(); 1222 InsertChar((char)key); 1223 return eKeyHandled; 1224 } 1225 1226 switch (key) { 1227 case KEY_RIGHT: 1228 MoveCursorRight(); 1229 ScrollRightIfNeeded(); 1230 return eKeyHandled; 1231 case KEY_LEFT: 1232 MoveCursorLeft(); 1233 ScrollLeftIfNeeded(); 1234 return eKeyHandled; 1235 case KEY_BACKSPACE: 1236 ClearError(); 1237 RemoveChar(); 1238 return eKeyHandled; 1239 default: 1240 break; 1241 } 1242 return eKeyNotHandled; 1243 } 1244 1245 bool FieldDelegateHasError() override { return !m_error.empty(); } 1246 1247 void FieldDelegateExitCallback() override { 1248 if (!IsSpecified() && m_required) 1249 SetError("This field is required!"); 1250 } 1251 1252 bool IsSpecified() { return !m_content.empty(); } 1253 1254 void ClearError() { m_error.clear(); } 1255 1256 const std::string &GetError() { return m_error; } 1257 1258 void SetError(const char *error) { m_error = error; } 1259 1260 const std::string &GetText() { return m_content; } 1261 1262 protected: 1263 std::string m_label; 1264 bool m_required; 1265 // The position of the top left corner character of the border. 1266 std::string m_content; 1267 // The cursor position in the content string itself. Can be in the range 1268 // [0, GetContentLength()]. 1269 int m_cursor_position; 1270 // The index of the first visible character in the content. 1271 int m_first_visibile_char; 1272 // The width of the fields content that was last drawn. Width can change, so 1273 // this is used to determine if scrolling is needed dynamically. 1274 int m_last_drawn_content_width; 1275 // Optional error message. If empty, field is considered to have no error. 1276 std::string m_error; 1277 }; 1278 1279 class IntegerFieldDelegate : public TextFieldDelegate { 1280 public: 1281 IntegerFieldDelegate(const char *label, int content, bool required) 1282 : TextFieldDelegate(label, std::to_string(content).c_str(), required) {} 1283 1284 // Only accept digits. 1285 bool IsAcceptableChar(int key) override { return isdigit(key); } 1286 1287 // Returns the integer content of the field. 1288 int GetInteger() { return std::stoi(m_content); } 1289 }; 1290 1291 class FileFieldDelegate : public TextFieldDelegate { 1292 public: 1293 FileFieldDelegate(const char *label, const char *content, bool need_to_exist, 1294 bool required) 1295 : TextFieldDelegate(label, content, required), 1296 m_need_to_exist(need_to_exist) {} 1297 1298 void FieldDelegateExitCallback() override { 1299 TextFieldDelegate::FieldDelegateExitCallback(); 1300 if (!IsSpecified()) 1301 return; 1302 1303 if (!m_need_to_exist) 1304 return; 1305 1306 FileSpec file = GetResolvedFileSpec(); 1307 if (!FileSystem::Instance().Exists(file)) { 1308 SetError("File doesn't exist!"); 1309 return; 1310 } 1311 if (FileSystem::Instance().IsDirectory(file)) { 1312 SetError("Not a file!"); 1313 return; 1314 } 1315 } 1316 1317 FileSpec GetFileSpec() { 1318 FileSpec file_spec(GetPath()); 1319 return file_spec; 1320 } 1321 1322 FileSpec GetResolvedFileSpec() { 1323 FileSpec file_spec(GetPath()); 1324 FileSystem::Instance().Resolve(file_spec); 1325 return file_spec; 1326 } 1327 1328 const std::string &GetPath() { return m_content; } 1329 1330 protected: 1331 bool m_need_to_exist; 1332 }; 1333 1334 class DirectoryFieldDelegate : public TextFieldDelegate { 1335 public: 1336 DirectoryFieldDelegate(const char *label, const char *content, 1337 bool need_to_exist, bool required) 1338 : TextFieldDelegate(label, content, required), 1339 m_need_to_exist(need_to_exist) {} 1340 1341 void FieldDelegateExitCallback() override { 1342 TextFieldDelegate::FieldDelegateExitCallback(); 1343 if (!IsSpecified()) 1344 return; 1345 1346 if (!m_need_to_exist) 1347 return; 1348 1349 FileSpec file = GetResolvedFileSpec(); 1350 if (!FileSystem::Instance().Exists(file)) { 1351 SetError("Directory doesn't exist!"); 1352 return; 1353 } 1354 if (!FileSystem::Instance().IsDirectory(file)) { 1355 SetError("Not a directory!"); 1356 return; 1357 } 1358 } 1359 1360 FileSpec GetFileSpec() { 1361 FileSpec file_spec(GetPath()); 1362 return file_spec; 1363 } 1364 1365 FileSpec GetResolvedFileSpec() { 1366 FileSpec file_spec(GetPath()); 1367 FileSystem::Instance().Resolve(file_spec); 1368 return file_spec; 1369 } 1370 1371 const std::string &GetPath() { return m_content; } 1372 1373 protected: 1374 bool m_need_to_exist; 1375 }; 1376 1377 class ArchFieldDelegate : public TextFieldDelegate { 1378 public: 1379 ArchFieldDelegate(const char *label, const char *content, bool required) 1380 : TextFieldDelegate(label, content, required) {} 1381 1382 void FieldDelegateExitCallback() override { 1383 TextFieldDelegate::FieldDelegateExitCallback(); 1384 if (!IsSpecified()) 1385 return; 1386 1387 if (!GetArchSpec().IsValid()) 1388 SetError("Not a valid arch!"); 1389 } 1390 1391 const std::string &GetArchString() { return m_content; } 1392 1393 ArchSpec GetArchSpec() { return ArchSpec(GetArchString()); } 1394 }; 1395 1396 class BooleanFieldDelegate : public FieldDelegate { 1397 public: 1398 BooleanFieldDelegate(const char *label, bool content) 1399 : m_label(label), m_content(content) {} 1400 1401 // Boolean fields are drawn as checkboxes. 1402 // 1403 // [X] Label or [ ] Label 1404 1405 // Boolean fields are have a single line. 1406 int FieldDelegateGetHeight() override { return 1; } 1407 1408 void FieldDelegateDraw(SubPad &surface, bool is_selected) override { 1409 surface.MoveCursor(0, 0); 1410 surface.PutChar('['); 1411 if (is_selected) 1412 surface.AttributeOn(A_REVERSE); 1413 surface.PutChar(m_content ? ACS_DIAMOND : ' '); 1414 if (is_selected) 1415 surface.AttributeOff(A_REVERSE); 1416 surface.PutChar(']'); 1417 surface.PutChar(' '); 1418 surface.PutCString(m_label.c_str()); 1419 } 1420 1421 void ToggleContent() { m_content = !m_content; } 1422 1423 void SetContentToTrue() { m_content = true; } 1424 1425 void SetContentToFalse() { m_content = false; } 1426 1427 HandleCharResult FieldDelegateHandleChar(int key) override { 1428 switch (key) { 1429 case 't': 1430 case '1': 1431 SetContentToTrue(); 1432 return eKeyHandled; 1433 case 'f': 1434 case '0': 1435 SetContentToFalse(); 1436 return eKeyHandled; 1437 case ' ': 1438 case '\r': 1439 case '\n': 1440 case KEY_ENTER: 1441 ToggleContent(); 1442 return eKeyHandled; 1443 default: 1444 break; 1445 } 1446 return eKeyNotHandled; 1447 } 1448 1449 // Returns the boolean content of the field. 1450 bool GetBoolean() { return m_content; } 1451 1452 protected: 1453 std::string m_label; 1454 bool m_content; 1455 }; 1456 1457 class ChoicesFieldDelegate : public FieldDelegate { 1458 public: 1459 ChoicesFieldDelegate(const char *label, int number_of_visible_choices, 1460 std::vector<std::string> choices) 1461 : m_label(label), m_number_of_visible_choices(number_of_visible_choices), 1462 m_choices(choices), m_choice(0), m_first_visibile_choice(0) {} 1463 1464 // Choices fields are drawn as titles boxses of a number of visible choices. 1465 // The rest of the choices become visible as the user scroll. The selected 1466 // choice is denoted by a diamond as the first character. 1467 // 1468 // __[Label]___________ 1469 // |-Choice 1 | 1470 // | Choice 2 | 1471 // | Choice 3 | 1472 // |__________________| 1473 1474 // Choices field have two border characters plus the number of visible 1475 // choices. 1476 int FieldDelegateGetHeight() override { 1477 return m_number_of_visible_choices + 2; 1478 } 1479 1480 int GetNumberOfChoices() { return m_choices.size(); } 1481 1482 // Get the index of the last visible choice. 1483 int GetLastVisibleChoice() { 1484 int index = m_first_visibile_choice + m_number_of_visible_choices; 1485 return std::min(index, GetNumberOfChoices()) - 1; 1486 } 1487 1488 void DrawContent(SubPad &surface, bool is_selected) { 1489 int choices_to_draw = GetLastVisibleChoice() - m_first_visibile_choice + 1; 1490 for (int i = 0; i < choices_to_draw; i++) { 1491 surface.MoveCursor(0, i); 1492 int current_choice = m_first_visibile_choice + i; 1493 const char *text = m_choices[current_choice].c_str(); 1494 bool highlight = is_selected && current_choice == m_choice; 1495 if (highlight) 1496 surface.AttributeOn(A_REVERSE); 1497 surface.PutChar(current_choice == m_choice ? ACS_DIAMOND : ' '); 1498 surface.PutCString(text); 1499 if (highlight) 1500 surface.AttributeOff(A_REVERSE); 1501 } 1502 } 1503 1504 void FieldDelegateDraw(SubPad &surface, bool is_selected) override { 1505 UpdateScrolling(); 1506 1507 surface.TitledBox(m_label.c_str()); 1508 1509 Rect content_bounds = surface.GetFrame(); 1510 content_bounds.Inset(1, 1); 1511 SubPad content_surface = SubPad(surface, content_bounds); 1512 1513 DrawContent(content_surface, is_selected); 1514 } 1515 1516 void SelectPrevious() { 1517 if (m_choice > 0) 1518 m_choice--; 1519 } 1520 1521 void SelectNext() { 1522 if (m_choice < GetNumberOfChoices() - 1) 1523 m_choice++; 1524 } 1525 1526 void UpdateScrolling() { 1527 if (m_choice > GetLastVisibleChoice()) { 1528 m_first_visibile_choice = m_choice - (m_number_of_visible_choices - 1); 1529 return; 1530 } 1531 1532 if (m_choice < m_first_visibile_choice) 1533 m_first_visibile_choice = m_choice; 1534 } 1535 1536 HandleCharResult FieldDelegateHandleChar(int key) override { 1537 switch (key) { 1538 case KEY_UP: 1539 SelectPrevious(); 1540 return eKeyHandled; 1541 case KEY_DOWN: 1542 SelectNext(); 1543 return eKeyHandled; 1544 default: 1545 break; 1546 } 1547 return eKeyNotHandled; 1548 } 1549 1550 // Returns the content of the choice as a string. 1551 std::string GetChoiceContent() { return m_choices[m_choice]; } 1552 1553 // Returns the index of the choice. 1554 int GetChoice() { return m_choice; } 1555 1556 void SetChoice(const std::string &choice) { 1557 for (int i = 0; i < GetNumberOfChoices(); i++) { 1558 if (choice == m_choices[i]) { 1559 m_choice = i; 1560 return; 1561 } 1562 } 1563 } 1564 1565 protected: 1566 std::string m_label; 1567 int m_number_of_visible_choices; 1568 std::vector<std::string> m_choices; 1569 // The index of the selected choice. 1570 int m_choice; 1571 // The index of the first visible choice in the field. 1572 int m_first_visibile_choice; 1573 }; 1574 1575 class PlatformPluginFieldDelegate : public ChoicesFieldDelegate { 1576 public: 1577 PlatformPluginFieldDelegate(Debugger &debugger) 1578 : ChoicesFieldDelegate("Platform Plugin", 3, GetPossiblePluginNames()) { 1579 PlatformSP platform_sp = debugger.GetPlatformList().GetSelectedPlatform(); 1580 if (platform_sp) 1581 SetChoice(platform_sp->GetName().AsCString()); 1582 } 1583 1584 std::vector<std::string> GetPossiblePluginNames() { 1585 std::vector<std::string> names; 1586 size_t i = 0; 1587 while (auto name = PluginManager::GetPlatformPluginNameAtIndex(i++)) 1588 names.push_back(name); 1589 return names; 1590 } 1591 1592 std::string GetPluginName() { 1593 std::string plugin_name = GetChoiceContent(); 1594 return plugin_name; 1595 } 1596 }; 1597 1598 class ProcessPluginFieldDelegate : public ChoicesFieldDelegate { 1599 public: 1600 ProcessPluginFieldDelegate() 1601 : ChoicesFieldDelegate("Process Plugin", 3, GetPossiblePluginNames()) {} 1602 1603 std::vector<std::string> GetPossiblePluginNames() { 1604 std::vector<std::string> names; 1605 names.push_back("<default>"); 1606 1607 size_t i = 0; 1608 while (auto name = PluginManager::GetProcessPluginNameAtIndex(i++)) 1609 names.push_back(name); 1610 return names; 1611 } 1612 1613 std::string GetPluginName() { 1614 std::string plugin_name = GetChoiceContent(); 1615 if (plugin_name == "<default>") 1616 return ""; 1617 return plugin_name; 1618 } 1619 }; 1620 1621 template <class T> class ListFieldDelegate : public FieldDelegate { 1622 public: 1623 ListFieldDelegate(const char *label, T default_field) 1624 : m_label(label), m_default_field(default_field), m_selection_index(0), 1625 m_selection_type(SelectionType::NewButton) {} 1626 1627 // Signify which element is selected. If a field or a remove button is 1628 // selected, then m_selection_index signifies the particular field that 1629 // is selected or the field that the remove button belongs to. 1630 enum class SelectionType { Field, RemoveButton, NewButton }; 1631 1632 // A List field is drawn as a titled box of a number of other fields of the 1633 // same type. Each field has a Remove button next to it that removes the 1634 // corresponding field. Finally, the last line contains a New button to add a 1635 // new field. 1636 // 1637 // __[Label]___________ 1638 // | Field 0 [Remove] | 1639 // | Field 1 [Remove] | 1640 // | Field 2 [Remove] | 1641 // | [New] | 1642 // |__________________| 1643 1644 // List fields have two lines for border characters, 1 line for the New 1645 // button, and the total height of the available fields. 1646 int FieldDelegateGetHeight() override { 1647 // 2 border characters. 1648 int height = 2; 1649 // Total height of the fields. 1650 for (int i = 0; i < GetNumberOfFields(); i++) { 1651 height += m_fields[i].FieldDelegateGetHeight(); 1652 } 1653 // A line for the New button. 1654 height++; 1655 return height; 1656 } 1657 1658 ScrollContext FieldDelegateGetScrollContext() override { 1659 int height = FieldDelegateGetHeight(); 1660 if (m_selection_type == SelectionType::NewButton) 1661 return ScrollContext(height - 2, height - 1); 1662 1663 FieldDelegate &field = m_fields[m_selection_index]; 1664 ScrollContext context = field.FieldDelegateGetScrollContext(); 1665 1666 // Start at 1 because of the top border. 1667 int offset = 1; 1668 for (int i = 0; i < m_selection_index; i++) { 1669 offset += m_fields[i].FieldDelegateGetHeight(); 1670 } 1671 context.Offset(offset); 1672 1673 // If the scroll context is touching the top border, include it in the 1674 // context to show the label. 1675 if (context.start == 1) 1676 context.start--; 1677 1678 // If the scroll context is touching the new button, include it as well as 1679 // the bottom border in the context. 1680 if (context.end == height - 3) 1681 context.end += 2; 1682 1683 return context; 1684 } 1685 1686 void DrawRemoveButton(SubPad &surface, int highlight) { 1687 surface.MoveCursor(1, surface.GetHeight() / 2); 1688 if (highlight) 1689 surface.AttributeOn(A_REVERSE); 1690 surface.PutCString("[Remove]"); 1691 if (highlight) 1692 surface.AttributeOff(A_REVERSE); 1693 } 1694 1695 void DrawFields(SubPad &surface, bool is_selected) { 1696 int line = 0; 1697 int width = surface.GetWidth(); 1698 for (int i = 0; i < GetNumberOfFields(); i++) { 1699 int height = m_fields[i].FieldDelegateGetHeight(); 1700 Rect bounds = Rect(Point(0, line), Size(width, height)); 1701 Rect field_bounds, remove_button_bounds; 1702 bounds.VerticalSplit(bounds.size.width - sizeof(" [Remove]"), 1703 field_bounds, remove_button_bounds); 1704 SubPad field_surface = SubPad(surface, field_bounds); 1705 SubPad remove_button_surface = SubPad(surface, remove_button_bounds); 1706 1707 bool is_element_selected = m_selection_index == i && is_selected; 1708 bool is_field_selected = 1709 is_element_selected && m_selection_type == SelectionType::Field; 1710 bool is_remove_button_selected = 1711 is_element_selected && 1712 m_selection_type == SelectionType::RemoveButton; 1713 m_fields[i].FieldDelegateDraw(field_surface, is_field_selected); 1714 DrawRemoveButton(remove_button_surface, is_remove_button_selected); 1715 1716 line += height; 1717 } 1718 } 1719 1720 void DrawNewButton(SubPad &surface, bool is_selected) { 1721 const char *button_text = "[New]"; 1722 int x = (surface.GetWidth() - sizeof(button_text) - 1) / 2; 1723 surface.MoveCursor(x, 0); 1724 bool highlight = 1725 is_selected && m_selection_type == SelectionType::NewButton; 1726 if (highlight) 1727 surface.AttributeOn(A_REVERSE); 1728 surface.PutCString(button_text); 1729 if (highlight) 1730 surface.AttributeOff(A_REVERSE); 1731 } 1732 1733 void FieldDelegateDraw(SubPad &surface, bool is_selected) override { 1734 surface.TitledBox(m_label.c_str()); 1735 1736 Rect content_bounds = surface.GetFrame(); 1737 content_bounds.Inset(1, 1); 1738 Rect fields_bounds, new_button_bounds; 1739 content_bounds.HorizontalSplit(content_bounds.size.height - 1, 1740 fields_bounds, new_button_bounds); 1741 SubPad fields_surface = SubPad(surface, fields_bounds); 1742 SubPad new_button_surface = SubPad(surface, new_button_bounds); 1743 1744 DrawFields(fields_surface, is_selected); 1745 DrawNewButton(new_button_surface, is_selected); 1746 } 1747 1748 void AddNewField() { 1749 m_fields.push_back(m_default_field); 1750 m_selection_index = GetNumberOfFields() - 1; 1751 m_selection_type = SelectionType::Field; 1752 FieldDelegate &field = m_fields[m_selection_index]; 1753 field.FieldDelegateSelectFirstElement(); 1754 } 1755 1756 void RemoveField() { 1757 m_fields.erase(m_fields.begin() + m_selection_index); 1758 if (m_selection_index != 0) 1759 m_selection_index--; 1760 1761 if (GetNumberOfFields() > 0) { 1762 m_selection_type = SelectionType::Field; 1763 FieldDelegate &field = m_fields[m_selection_index]; 1764 field.FieldDelegateSelectFirstElement(); 1765 } else 1766 m_selection_type = SelectionType::NewButton; 1767 } 1768 1769 HandleCharResult SelectNext(int key) { 1770 if (m_selection_type == SelectionType::NewButton) 1771 return eKeyNotHandled; 1772 1773 if (m_selection_type == SelectionType::RemoveButton) { 1774 if (m_selection_index == GetNumberOfFields() - 1) { 1775 m_selection_type = SelectionType::NewButton; 1776 return eKeyHandled; 1777 } 1778 m_selection_index++; 1779 m_selection_type = SelectionType::Field; 1780 FieldDelegate &next_field = m_fields[m_selection_index]; 1781 next_field.FieldDelegateSelectFirstElement(); 1782 return eKeyHandled; 1783 } 1784 1785 FieldDelegate &field = m_fields[m_selection_index]; 1786 if (!field.FieldDelegateOnLastOrOnlyElement()) { 1787 return field.FieldDelegateHandleChar(key); 1788 } 1789 1790 field.FieldDelegateExitCallback(); 1791 1792 m_selection_type = SelectionType::RemoveButton; 1793 return eKeyHandled; 1794 } 1795 1796 HandleCharResult SelectPrevious(int key) { 1797 if (FieldDelegateOnFirstOrOnlyElement()) 1798 return eKeyNotHandled; 1799 1800 if (m_selection_type == SelectionType::RemoveButton) { 1801 m_selection_type = SelectionType::Field; 1802 FieldDelegate &field = m_fields[m_selection_index]; 1803 field.FieldDelegateSelectLastElement(); 1804 return eKeyHandled; 1805 } 1806 1807 if (m_selection_type == SelectionType::NewButton) { 1808 m_selection_type = SelectionType::RemoveButton; 1809 m_selection_index = GetNumberOfFields() - 1; 1810 return eKeyHandled; 1811 } 1812 1813 FieldDelegate &field = m_fields[m_selection_index]; 1814 if (!field.FieldDelegateOnFirstOrOnlyElement()) { 1815 return field.FieldDelegateHandleChar(key); 1816 } 1817 1818 field.FieldDelegateExitCallback(); 1819 1820 m_selection_type = SelectionType::RemoveButton; 1821 m_selection_index--; 1822 return eKeyHandled; 1823 } 1824 1825 HandleCharResult FieldDelegateHandleChar(int key) override { 1826 switch (key) { 1827 case '\r': 1828 case '\n': 1829 case KEY_ENTER: 1830 switch (m_selection_type) { 1831 case SelectionType::NewButton: 1832 AddNewField(); 1833 return eKeyHandled; 1834 case SelectionType::RemoveButton: 1835 RemoveField(); 1836 return eKeyHandled; 1837 default: 1838 break; 1839 } 1840 break; 1841 case '\t': 1842 SelectNext(key); 1843 return eKeyHandled; 1844 case KEY_SHIFT_TAB: 1845 SelectPrevious(key); 1846 return eKeyHandled; 1847 default: 1848 break; 1849 } 1850 1851 // If the key wasn't handled and one of the fields is selected, pass the key 1852 // to that field. 1853 if (m_selection_type == SelectionType::Field) { 1854 return m_fields[m_selection_index].FieldDelegateHandleChar(key); 1855 } 1856 1857 return eKeyNotHandled; 1858 } 1859 1860 bool FieldDelegateOnLastOrOnlyElement() override { 1861 if (m_selection_type == SelectionType::NewButton) { 1862 return true; 1863 } 1864 return false; 1865 } 1866 1867 bool FieldDelegateOnFirstOrOnlyElement() override { 1868 if (m_selection_type == SelectionType::NewButton && 1869 GetNumberOfFields() == 0) 1870 return true; 1871 1872 if (m_selection_type == SelectionType::Field && m_selection_index == 0) { 1873 FieldDelegate &field = m_fields[m_selection_index]; 1874 return field.FieldDelegateOnFirstOrOnlyElement(); 1875 } 1876 1877 return false; 1878 } 1879 1880 void FieldDelegateSelectFirstElement() override { 1881 if (GetNumberOfFields() == 0) { 1882 m_selection_type = SelectionType::NewButton; 1883 return; 1884 } 1885 1886 m_selection_type = SelectionType::Field; 1887 m_selection_index = 0; 1888 } 1889 1890 void FieldDelegateSelectLastElement() override { 1891 m_selection_type = SelectionType::NewButton; 1892 return; 1893 } 1894 1895 int GetNumberOfFields() { return m_fields.size(); } 1896 1897 // Returns the form delegate at the current index. 1898 T &GetField(int index) { return m_fields[index]; } 1899 1900 protected: 1901 std::string m_label; 1902 // The default field delegate instance from which new field delegates will be 1903 // created though a copy. 1904 T m_default_field; 1905 std::vector<T> m_fields; 1906 int m_selection_index; 1907 // See SelectionType class enum. 1908 SelectionType m_selection_type; 1909 }; 1910 1911 class FormAction { 1912 public: 1913 FormAction(const char *label, std::function<void(Window &)> action) 1914 : m_action(action) { 1915 if (label) 1916 m_label = label; 1917 } 1918 1919 // Draw a centered [Label]. 1920 void Draw(SubPad &surface, bool is_selected) { 1921 int x = (surface.GetWidth() - m_label.length()) / 2; 1922 surface.MoveCursor(x, 0); 1923 if (is_selected) 1924 surface.AttributeOn(A_REVERSE); 1925 surface.PutChar('['); 1926 surface.PutCString(m_label.c_str()); 1927 surface.PutChar(']'); 1928 if (is_selected) 1929 surface.AttributeOff(A_REVERSE); 1930 } 1931 1932 void Execute(Window &window) { m_action(window); } 1933 1934 const std::string &GetLabel() { return m_label; } 1935 1936 protected: 1937 std::string m_label; 1938 std::function<void(Window &)> m_action; 1939 }; 1940 1941 class FormDelegate { 1942 public: 1943 FormDelegate() {} 1944 1945 virtual ~FormDelegate() = default; 1946 1947 virtual std::string GetName() = 0; 1948 1949 virtual void UpdateFieldsVisibility() { return; } 1950 1951 FieldDelegate *GetField(uint32_t field_index) { 1952 if (field_index < m_fields.size()) 1953 return m_fields[field_index].get(); 1954 return nullptr; 1955 } 1956 1957 FormAction &GetAction(int action_index) { return m_actions[action_index]; } 1958 1959 int GetNumberOfFields() { return m_fields.size(); } 1960 1961 int GetNumberOfActions() { return m_actions.size(); } 1962 1963 bool HasError() { return !m_error.empty(); } 1964 1965 void ClearError() { m_error.clear(); } 1966 1967 const std::string &GetError() { return m_error; } 1968 1969 void SetError(const char *error) { m_error = error; } 1970 1971 // If all fields are valid, true is returned. Otherwise, an error message is 1972 // set and false is returned. This method is usually called at the start of an 1973 // action that requires valid fields. 1974 bool CheckFieldsValidity() { 1975 for (int i = 0; i < GetNumberOfFields(); i++) { 1976 if (GetField(i)->FieldDelegateHasError()) { 1977 SetError("Some fields are invalid!"); 1978 return false; 1979 } 1980 } 1981 return true; 1982 } 1983 1984 // Factory methods to create and add fields of specific types. 1985 1986 TextFieldDelegate *AddTextField(const char *label, const char *content, 1987 bool required) { 1988 TextFieldDelegate *delegate = 1989 new TextFieldDelegate(label, content, required); 1990 m_fields.push_back(FieldDelegateUP(delegate)); 1991 return delegate; 1992 } 1993 1994 FileFieldDelegate *AddFileField(const char *label, const char *content, 1995 bool need_to_exist, bool required) { 1996 FileFieldDelegate *delegate = 1997 new FileFieldDelegate(label, content, need_to_exist, required); 1998 m_fields.push_back(FieldDelegateUP(delegate)); 1999 return delegate; 2000 } 2001 2002 DirectoryFieldDelegate *AddDirectoryField(const char *label, 2003 const char *content, 2004 bool need_to_exist, bool required) { 2005 DirectoryFieldDelegate *delegate = 2006 new DirectoryFieldDelegate(label, content, need_to_exist, required); 2007 m_fields.push_back(FieldDelegateUP(delegate)); 2008 return delegate; 2009 } 2010 2011 ArchFieldDelegate *AddArchField(const char *label, const char *content, 2012 bool required) { 2013 ArchFieldDelegate *delegate = 2014 new ArchFieldDelegate(label, content, required); 2015 m_fields.push_back(FieldDelegateUP(delegate)); 2016 return delegate; 2017 } 2018 2019 IntegerFieldDelegate *AddIntegerField(const char *label, int content, 2020 bool required) { 2021 IntegerFieldDelegate *delegate = 2022 new IntegerFieldDelegate(label, content, required); 2023 m_fields.push_back(FieldDelegateUP(delegate)); 2024 return delegate; 2025 } 2026 2027 BooleanFieldDelegate *AddBooleanField(const char *label, bool content) { 2028 BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content); 2029 m_fields.push_back(FieldDelegateUP(delegate)); 2030 return delegate; 2031 } 2032 2033 ChoicesFieldDelegate *AddChoicesField(const char *label, int height, 2034 std::vector<std::string> choices) { 2035 ChoicesFieldDelegate *delegate = 2036 new ChoicesFieldDelegate(label, height, choices); 2037 m_fields.push_back(FieldDelegateUP(delegate)); 2038 return delegate; 2039 } 2040 2041 PlatformPluginFieldDelegate *AddPlatformPluginField(Debugger &debugger) { 2042 PlatformPluginFieldDelegate *delegate = 2043 new PlatformPluginFieldDelegate(debugger); 2044 m_fields.push_back(FieldDelegateUP(delegate)); 2045 return delegate; 2046 } 2047 2048 ProcessPluginFieldDelegate *AddProcessPluginField() { 2049 ProcessPluginFieldDelegate *delegate = new ProcessPluginFieldDelegate(); 2050 m_fields.push_back(FieldDelegateUP(delegate)); 2051 return delegate; 2052 } 2053 2054 template <class T> 2055 ListFieldDelegate<T> *AddListField(const char *label, T default_field) { 2056 ListFieldDelegate<T> *delegate = 2057 new ListFieldDelegate<T>(label, default_field); 2058 m_fields.push_back(FieldDelegateUP(delegate)); 2059 return delegate; 2060 } 2061 2062 // Factory methods for adding actions. 2063 2064 void AddAction(const char *label, std::function<void(Window &)> action) { 2065 m_actions.push_back(FormAction(label, action)); 2066 } 2067 2068 protected: 2069 std::vector<FieldDelegateUP> m_fields; 2070 std::vector<FormAction> m_actions; 2071 // Optional error message. If empty, form is considered to have no error. 2072 std::string m_error; 2073 }; 2074 2075 typedef std::shared_ptr<FormDelegate> FormDelegateSP; 2076 2077 class FormWindowDelegate : public WindowDelegate { 2078 public: 2079 FormWindowDelegate(FormDelegateSP &delegate_sp) 2080 : m_delegate_sp(delegate_sp), m_selection_index(0), 2081 m_first_visible_line(0) { 2082 assert(m_delegate_sp->GetNumberOfActions() > 0); 2083 if (m_delegate_sp->GetNumberOfFields() > 0) 2084 m_selection_type = SelectionType::Field; 2085 else 2086 m_selection_type = SelectionType::Action; 2087 } 2088 2089 // Signify which element is selected. If a field or an action is selected, 2090 // then m_selection_index signifies the particular field or action that is 2091 // selected. 2092 enum class SelectionType { Field, Action }; 2093 2094 // A form window is padded by one character from all sides. First, if an error 2095 // message exists, it is drawn followed by a separator. Then one or more 2096 // fields are drawn. Finally, all available actions are drawn on a single 2097 // line. 2098 // 2099 // ___<Form Name>_________________________________________________ 2100 // | | 2101 // | - Error message if it exists. | 2102 // |-------------------------------------------------------------| 2103 // | Form elements here. | 2104 // | Form actions here. | 2105 // | | 2106 // |______________________________________[Press Esc to cancel]__| 2107 // 2108 2109 // One line for the error and another for the horizontal line. 2110 int GetErrorHeight() { 2111 if (m_delegate_sp->HasError()) 2112 return 2; 2113 return 0; 2114 } 2115 2116 // Actions span a single line. 2117 int GetActionsHeight() { 2118 if (m_delegate_sp->GetNumberOfActions() > 0) 2119 return 1; 2120 return 0; 2121 } 2122 2123 // Get the total number of needed lines to draw the contents. 2124 int GetContentHeight() { 2125 int height = 0; 2126 height += GetErrorHeight(); 2127 for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) { 2128 if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible()) 2129 continue; 2130 height += m_delegate_sp->GetField(i)->FieldDelegateGetHeight(); 2131 } 2132 height += GetActionsHeight(); 2133 return height; 2134 } 2135 2136 ScrollContext GetScrollContext() { 2137 if (m_selection_type == SelectionType::Action) 2138 return ScrollContext(GetContentHeight() - 1); 2139 2140 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); 2141 ScrollContext context = field->FieldDelegateGetScrollContext(); 2142 2143 int offset = GetErrorHeight(); 2144 for (int i = 0; i < m_selection_index; i++) { 2145 if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible()) 2146 continue; 2147 offset += m_delegate_sp->GetField(i)->FieldDelegateGetHeight(); 2148 } 2149 context.Offset(offset); 2150 2151 // If the context is touching the error, include the error in the context as 2152 // well. 2153 if (context.start == GetErrorHeight()) 2154 context.start = 0; 2155 2156 return context; 2157 } 2158 2159 void UpdateScrolling(DerivedWindow &surface) { 2160 ScrollContext context = GetScrollContext(); 2161 int content_height = GetContentHeight(); 2162 int surface_height = surface.GetHeight(); 2163 int visible_height = std::min(content_height, surface_height); 2164 int last_visible_line = m_first_visible_line + visible_height - 1; 2165 2166 // If the last visible line is bigger than the content, then it is invalid 2167 // and needs to be set to the last line in the content. This can happen when 2168 // a field has shrunk in height. 2169 if (last_visible_line > content_height - 1) { 2170 m_first_visible_line = content_height - visible_height; 2171 } 2172 2173 if (context.start < m_first_visible_line) { 2174 m_first_visible_line = context.start; 2175 return; 2176 } 2177 2178 if (context.end > last_visible_line) { 2179 m_first_visible_line = context.end - visible_height + 1; 2180 } 2181 } 2182 2183 void DrawError(SubPad &surface) { 2184 if (!m_delegate_sp->HasError()) 2185 return; 2186 surface.MoveCursor(0, 0); 2187 surface.AttributeOn(COLOR_PAIR(RedOnBlack)); 2188 surface.PutChar(ACS_DIAMOND); 2189 surface.PutChar(' '); 2190 surface.PutCStringTruncated(1, m_delegate_sp->GetError().c_str()); 2191 surface.AttributeOff(COLOR_PAIR(RedOnBlack)); 2192 2193 surface.MoveCursor(0, 1); 2194 surface.HorizontalLine(surface.GetWidth()); 2195 } 2196 2197 void DrawFields(SubPad &surface) { 2198 int line = 0; 2199 int width = surface.GetWidth(); 2200 bool a_field_is_selected = m_selection_type == SelectionType::Field; 2201 for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) { 2202 FieldDelegate *field = m_delegate_sp->GetField(i); 2203 if (!field->FieldDelegateIsVisible()) 2204 continue; 2205 bool is_field_selected = a_field_is_selected && m_selection_index == i; 2206 int height = field->FieldDelegateGetHeight(); 2207 Rect bounds = Rect(Point(0, line), Size(width, height)); 2208 SubPad field_surface = SubPad(surface, bounds); 2209 field->FieldDelegateDraw(field_surface, is_field_selected); 2210 line += height; 2211 } 2212 } 2213 2214 void DrawActions(SubPad &surface) { 2215 int number_of_actions = m_delegate_sp->GetNumberOfActions(); 2216 int width = surface.GetWidth() / number_of_actions; 2217 bool an_action_is_selected = m_selection_type == SelectionType::Action; 2218 int x = 0; 2219 for (int i = 0; i < number_of_actions; i++) { 2220 bool is_action_selected = an_action_is_selected && m_selection_index == i; 2221 FormAction &action = m_delegate_sp->GetAction(i); 2222 Rect bounds = Rect(Point(x, 0), Size(width, 1)); 2223 SubPad action_surface = SubPad(surface, bounds); 2224 action.Draw(action_surface, is_action_selected); 2225 x += width; 2226 } 2227 } 2228 2229 void DrawElements(SubPad &surface) { 2230 Rect frame = surface.GetFrame(); 2231 Rect fields_bounds, actions_bounds; 2232 frame.HorizontalSplit(surface.GetHeight() - GetActionsHeight(), 2233 fields_bounds, actions_bounds); 2234 SubPad fields_surface = SubPad(surface, fields_bounds); 2235 SubPad actions_surface = SubPad(surface, actions_bounds); 2236 2237 DrawFields(fields_surface); 2238 DrawActions(actions_surface); 2239 } 2240 2241 // Contents are first drawn on a pad. Then a subset of that pad is copied to 2242 // the derived window starting at the first visible line. This essentially 2243 // provides scrolling functionality. 2244 void DrawContent(DerivedWindow &surface) { 2245 UpdateScrolling(surface); 2246 2247 int width = surface.GetWidth(); 2248 int height = GetContentHeight(); 2249 Pad pad = Pad(Size(width, height)); 2250 2251 Rect frame = pad.GetFrame(); 2252 Rect error_bounds, elements_bounds; 2253 frame.HorizontalSplit(GetErrorHeight(), error_bounds, elements_bounds); 2254 SubPad error_surface = SubPad(pad, error_bounds); 2255 SubPad elements_surface = SubPad(pad, elements_bounds); 2256 2257 DrawError(error_surface); 2258 DrawElements(elements_surface); 2259 2260 int copy_height = std::min(surface.GetHeight(), pad.GetHeight()); 2261 pad.CopyToSurface(surface, Point(0, m_first_visible_line), Point(), 2262 Size(width, copy_height)); 2263 } 2264 2265 bool WindowDelegateDraw(Window &window, bool force) override { 2266 m_delegate_sp->UpdateFieldsVisibility(); 2267 2268 window.Erase(); 2269 2270 window.DrawTitleBox(m_delegate_sp->GetName().c_str(), 2271 "Press Esc to cancel"); 2272 2273 Rect content_bounds = window.GetFrame(); 2274 content_bounds.Inset(2, 2); 2275 DerivedWindow content_surface = DerivedWindow(window, content_bounds); 2276 2277 DrawContent(content_surface); 2278 return true; 2279 } 2280 2281 void SkipNextHiddenFields() { 2282 while (true) { 2283 if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible()) 2284 return; 2285 2286 if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) { 2287 m_selection_type = SelectionType::Action; 2288 m_selection_index = 0; 2289 return; 2290 } 2291 2292 m_selection_index++; 2293 } 2294 } 2295 2296 HandleCharResult SelectNext(int key) { 2297 if (m_selection_type == SelectionType::Action) { 2298 if (m_selection_index < m_delegate_sp->GetNumberOfActions() - 1) { 2299 m_selection_index++; 2300 return eKeyHandled; 2301 } 2302 2303 m_selection_index = 0; 2304 m_selection_type = SelectionType::Field; 2305 SkipNextHiddenFields(); 2306 if (m_selection_type == SelectionType::Field) { 2307 FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index); 2308 next_field->FieldDelegateSelectFirstElement(); 2309 } 2310 return eKeyHandled; 2311 } 2312 2313 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); 2314 if (!field->FieldDelegateOnLastOrOnlyElement()) { 2315 return field->FieldDelegateHandleChar(key); 2316 } 2317 2318 field->FieldDelegateExitCallback(); 2319 2320 if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) { 2321 m_selection_type = SelectionType::Action; 2322 m_selection_index = 0; 2323 return eKeyHandled; 2324 } 2325 2326 m_selection_index++; 2327 SkipNextHiddenFields(); 2328 2329 if (m_selection_type == SelectionType::Field) { 2330 FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index); 2331 next_field->FieldDelegateSelectFirstElement(); 2332 } 2333 2334 return eKeyHandled; 2335 } 2336 2337 void SkipPreviousHiddenFields() { 2338 while (true) { 2339 if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible()) 2340 return; 2341 2342 if (m_selection_index == 0) { 2343 m_selection_type = SelectionType::Action; 2344 m_selection_index = 0; 2345 return; 2346 } 2347 2348 m_selection_index--; 2349 } 2350 } 2351 2352 HandleCharResult SelectPrevious(int key) { 2353 if (m_selection_type == SelectionType::Action) { 2354 if (m_selection_index > 0) { 2355 m_selection_index--; 2356 return eKeyHandled; 2357 } 2358 m_selection_index = m_delegate_sp->GetNumberOfFields() - 1; 2359 m_selection_type = SelectionType::Field; 2360 SkipPreviousHiddenFields(); 2361 if (m_selection_type == SelectionType::Field) { 2362 FieldDelegate *previous_field = 2363 m_delegate_sp->GetField(m_selection_index); 2364 previous_field->FieldDelegateSelectLastElement(); 2365 } 2366 return eKeyHandled; 2367 } 2368 2369 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); 2370 if (!field->FieldDelegateOnFirstOrOnlyElement()) { 2371 return field->FieldDelegateHandleChar(key); 2372 } 2373 2374 field->FieldDelegateExitCallback(); 2375 2376 if (m_selection_index == 0) { 2377 m_selection_type = SelectionType::Action; 2378 m_selection_index = m_delegate_sp->GetNumberOfActions() - 1; 2379 return eKeyHandled; 2380 } 2381 2382 m_selection_index--; 2383 SkipPreviousHiddenFields(); 2384 2385 if (m_selection_type == SelectionType::Field) { 2386 FieldDelegate *previous_field = 2387 m_delegate_sp->GetField(m_selection_index); 2388 previous_field->FieldDelegateSelectLastElement(); 2389 } 2390 2391 return eKeyHandled; 2392 } 2393 2394 void ExecuteAction(Window &window) { 2395 FormAction &action = m_delegate_sp->GetAction(m_selection_index); 2396 action.Execute(window); 2397 if (m_delegate_sp->HasError()) { 2398 m_first_visible_line = 0; 2399 m_selection_index = 0; 2400 m_selection_type = SelectionType::Field; 2401 } 2402 } 2403 2404 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { 2405 switch (key) { 2406 case '\r': 2407 case '\n': 2408 case KEY_ENTER: 2409 if (m_selection_type == SelectionType::Action) { 2410 ExecuteAction(window); 2411 return eKeyHandled; 2412 } 2413 break; 2414 case '\t': 2415 return SelectNext(key); 2416 case KEY_SHIFT_TAB: 2417 return SelectPrevious(key); 2418 case KEY_ESCAPE: 2419 window.GetParent()->RemoveSubWindow(&window); 2420 return eKeyHandled; 2421 default: 2422 break; 2423 } 2424 2425 // If the key wasn't handled and one of the fields is selected, pass the key 2426 // to that field. 2427 if (m_selection_type == SelectionType::Field) { 2428 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); 2429 return field->FieldDelegateHandleChar(key); 2430 } 2431 2432 return eKeyNotHandled; 2433 } 2434 2435 protected: 2436 FormDelegateSP m_delegate_sp; 2437 // The index of the currently selected SelectionType. 2438 int m_selection_index; 2439 // See SelectionType class enum. 2440 SelectionType m_selection_type; 2441 // The first visible line from the pad. 2442 int m_first_visible_line; 2443 }; 2444 2445 /////////////////////////// 2446 // Form Delegate Instances 2447 /////////////////////////// 2448 2449 class DetachOrKillProcessFormDelegate : public FormDelegate { 2450 public: 2451 DetachOrKillProcessFormDelegate(Process *process) : m_process(process) { 2452 SetError("There is a running process, either detach or kill it."); 2453 2454 m_keep_stopped_field = 2455 AddBooleanField("Keep process stopped when detaching.", false); 2456 2457 AddAction("Detach", [this](Window &window) { Detach(window); }); 2458 AddAction("Kill", [this](Window &window) { Kill(window); }); 2459 } 2460 2461 std::string GetName() override { return "Detach/Kill Process"; } 2462 2463 void Kill(Window &window) { 2464 Status destroy_status(m_process->Destroy(false)); 2465 if (destroy_status.Fail()) { 2466 SetError("Failed to kill process."); 2467 return; 2468 } 2469 window.GetParent()->RemoveSubWindow(&window); 2470 } 2471 2472 void Detach(Window &window) { 2473 Status detach_status(m_process->Detach(m_keep_stopped_field->GetBoolean())); 2474 if (detach_status.Fail()) { 2475 SetError("Failed to detach from process."); 2476 return; 2477 } 2478 window.GetParent()->RemoveSubWindow(&window); 2479 } 2480 2481 protected: 2482 Process *m_process; 2483 BooleanFieldDelegate *m_keep_stopped_field; 2484 }; 2485 2486 class ProcessAttachFormDelegate : public FormDelegate { 2487 public: 2488 ProcessAttachFormDelegate(Debugger &debugger, WindowSP main_window_sp) 2489 : m_debugger(debugger), m_main_window_sp(main_window_sp) { 2490 std::vector<std::string> types; 2491 types.push_back(std::string("Name")); 2492 types.push_back(std::string("PID")); 2493 m_type_field = AddChoicesField("Attach By", 2, types); 2494 m_pid_field = AddIntegerField("PID", 0, true); 2495 m_name_field = 2496 AddTextField("Process Name", GetDefaultProcessName().c_str(), true); 2497 m_continue_field = AddBooleanField("Continue once attached.", false); 2498 m_wait_for_field = AddBooleanField("Wait for process to launch.", false); 2499 m_include_existing_field = 2500 AddBooleanField("Include existing processes.", false); 2501 m_show_advanced_field = AddBooleanField("Show advanced settings.", false); 2502 m_plugin_field = AddProcessPluginField(); 2503 2504 AddAction("Attach", [this](Window &window) { Attach(window); }); 2505 } 2506 2507 std::string GetName() override { return "Attach Process"; } 2508 2509 void UpdateFieldsVisibility() override { 2510 if (m_type_field->GetChoiceContent() == "Name") { 2511 m_pid_field->FieldDelegateHide(); 2512 m_name_field->FieldDelegateShow(); 2513 m_wait_for_field->FieldDelegateShow(); 2514 if (m_wait_for_field->GetBoolean()) 2515 m_include_existing_field->FieldDelegateShow(); 2516 else 2517 m_include_existing_field->FieldDelegateHide(); 2518 } else { 2519 m_pid_field->FieldDelegateShow(); 2520 m_name_field->FieldDelegateHide(); 2521 m_wait_for_field->FieldDelegateHide(); 2522 m_include_existing_field->FieldDelegateHide(); 2523 } 2524 if (m_show_advanced_field->GetBoolean()) 2525 m_plugin_field->FieldDelegateShow(); 2526 else 2527 m_plugin_field->FieldDelegateHide(); 2528 } 2529 2530 // Get the basename of the target's main executable if available, empty string 2531 // otherwise. 2532 std::string GetDefaultProcessName() { 2533 Target *target = m_debugger.GetSelectedTarget().get(); 2534 if (target == nullptr) 2535 return ""; 2536 2537 ModuleSP module_sp = target->GetExecutableModule(); 2538 if (!module_sp->IsExecutable()) 2539 return ""; 2540 2541 return module_sp->GetFileSpec().GetFilename().AsCString(); 2542 } 2543 2544 bool StopRunningProcess() { 2545 ExecutionContext exe_ctx = 2546 m_debugger.GetCommandInterpreter().GetExecutionContext(); 2547 2548 if (!exe_ctx.HasProcessScope()) 2549 return false; 2550 2551 Process *process = exe_ctx.GetProcessPtr(); 2552 if (!(process && process->IsAlive())) 2553 return false; 2554 2555 FormDelegateSP form_delegate_sp = 2556 FormDelegateSP(new DetachOrKillProcessFormDelegate(process)); 2557 Rect bounds = m_main_window_sp->GetCenteredRect(85, 8); 2558 WindowSP form_window_sp = m_main_window_sp->CreateSubWindow( 2559 form_delegate_sp->GetName().c_str(), bounds, true); 2560 WindowDelegateSP window_delegate_sp = 2561 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); 2562 form_window_sp->SetDelegate(window_delegate_sp); 2563 2564 return true; 2565 } 2566 2567 Target *GetTarget() { 2568 Target *target = m_debugger.GetSelectedTarget().get(); 2569 2570 if (target != nullptr) 2571 return target; 2572 2573 TargetSP new_target_sp; 2574 m_debugger.GetTargetList().CreateTarget( 2575 m_debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp); 2576 2577 target = new_target_sp.get(); 2578 2579 if (target == nullptr) 2580 SetError("Failed to create target."); 2581 2582 m_debugger.GetTargetList().SetSelectedTarget(new_target_sp); 2583 2584 return target; 2585 } 2586 2587 ProcessAttachInfo GetAttachInfo() { 2588 ProcessAttachInfo attach_info; 2589 attach_info.SetContinueOnceAttached(m_continue_field->GetBoolean()); 2590 if (m_type_field->GetChoiceContent() == "Name") { 2591 attach_info.GetExecutableFile().SetFile(m_name_field->GetText(), 2592 FileSpec::Style::native); 2593 attach_info.SetWaitForLaunch(m_wait_for_field->GetBoolean()); 2594 if (m_wait_for_field->GetBoolean()) 2595 attach_info.SetIgnoreExisting(!m_include_existing_field->GetBoolean()); 2596 } else { 2597 attach_info.SetProcessID(m_pid_field->GetInteger()); 2598 } 2599 attach_info.SetProcessPluginName(m_plugin_field->GetPluginName()); 2600 2601 return attach_info; 2602 } 2603 2604 void Attach(Window &window) { 2605 ClearError(); 2606 2607 bool all_fields_are_valid = CheckFieldsValidity(); 2608 if (!all_fields_are_valid) 2609 return; 2610 2611 bool process_is_running = StopRunningProcess(); 2612 if (process_is_running) 2613 return; 2614 2615 Target *target = GetTarget(); 2616 if (HasError()) 2617 return; 2618 2619 StreamString stream; 2620 ProcessAttachInfo attach_info = GetAttachInfo(); 2621 Status status = target->Attach(attach_info, &stream); 2622 2623 if (status.Fail()) { 2624 SetError(status.AsCString()); 2625 return; 2626 } 2627 2628 ProcessSP process_sp(target->GetProcessSP()); 2629 if (!process_sp) { 2630 SetError("Attached sucessfully but target has no process."); 2631 return; 2632 } 2633 2634 if (attach_info.GetContinueOnceAttached()) 2635 process_sp->Resume(); 2636 2637 window.GetParent()->RemoveSubWindow(&window); 2638 } 2639 2640 protected: 2641 Debugger &m_debugger; 2642 WindowSP m_main_window_sp; 2643 2644 ChoicesFieldDelegate *m_type_field; 2645 IntegerFieldDelegate *m_pid_field; 2646 TextFieldDelegate *m_name_field; 2647 BooleanFieldDelegate *m_continue_field; 2648 BooleanFieldDelegate *m_wait_for_field; 2649 BooleanFieldDelegate *m_include_existing_field; 2650 BooleanFieldDelegate *m_show_advanced_field; 2651 ProcessPluginFieldDelegate *m_plugin_field; 2652 }; 2653 2654 class MenuDelegate { 2655 public: 2656 virtual ~MenuDelegate() = default; 2657 2658 virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0; 2659 }; 2660 2661 class Menu : public WindowDelegate { 2662 public: 2663 enum class Type { Invalid, Bar, Item, Separator }; 2664 2665 // Menubar or separator constructor 2666 Menu(Type type); 2667 2668 // Menuitem constructor 2669 Menu(const char *name, const char *key_name, int key_value, 2670 uint64_t identifier); 2671 2672 ~Menu() override = default; 2673 2674 const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; } 2675 2676 void SetDelegate(const MenuDelegateSP &delegate_sp) { 2677 m_delegate_sp = delegate_sp; 2678 } 2679 2680 void RecalculateNameLengths(); 2681 2682 void AddSubmenu(const MenuSP &menu_sp); 2683 2684 int DrawAndRunMenu(Window &window); 2685 2686 void DrawMenuTitle(Window &window, bool highlight); 2687 2688 bool WindowDelegateDraw(Window &window, bool force) override; 2689 2690 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override; 2691 2692 MenuActionResult ActionPrivate(Menu &menu) { 2693 MenuActionResult result = MenuActionResult::NotHandled; 2694 if (m_delegate_sp) { 2695 result = m_delegate_sp->MenuDelegateAction(menu); 2696 if (result != MenuActionResult::NotHandled) 2697 return result; 2698 } else if (m_parent) { 2699 result = m_parent->ActionPrivate(menu); 2700 if (result != MenuActionResult::NotHandled) 2701 return result; 2702 } 2703 return m_canned_result; 2704 } 2705 2706 MenuActionResult Action() { 2707 // Call the recursive action so it can try to handle it with the menu 2708 // delegate, and if not, try our parent menu 2709 return ActionPrivate(*this); 2710 } 2711 2712 void SetCannedResult(MenuActionResult result) { m_canned_result = result; } 2713 2714 Menus &GetSubmenus() { return m_submenus; } 2715 2716 const Menus &GetSubmenus() const { return m_submenus; } 2717 2718 int GetSelectedSubmenuIndex() const { return m_selected; } 2719 2720 void SetSelectedSubmenuIndex(int idx) { m_selected = idx; } 2721 2722 Type GetType() const { return m_type; } 2723 2724 int GetStartingColumn() const { return m_start_col; } 2725 2726 void SetStartingColumn(int col) { m_start_col = col; } 2727 2728 int GetKeyValue() const { return m_key_value; } 2729 2730 std::string &GetName() { return m_name; } 2731 2732 int GetDrawWidth() const { 2733 return m_max_submenu_name_length + m_max_submenu_key_name_length + 8; 2734 } 2735 2736 uint64_t GetIdentifier() const { return m_identifier; } 2737 2738 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } 2739 2740 protected: 2741 std::string m_name; 2742 std::string m_key_name; 2743 uint64_t m_identifier; 2744 Type m_type; 2745 int m_key_value; 2746 int m_start_col; 2747 int m_max_submenu_name_length; 2748 int m_max_submenu_key_name_length; 2749 int m_selected; 2750 Menu *m_parent; 2751 Menus m_submenus; 2752 WindowSP m_menu_window_sp; 2753 MenuActionResult m_canned_result; 2754 MenuDelegateSP m_delegate_sp; 2755 }; 2756 2757 // Menubar or separator constructor 2758 Menu::Menu(Type type) 2759 : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0), 2760 m_start_col(0), m_max_submenu_name_length(0), 2761 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr), 2762 m_submenus(), m_canned_result(MenuActionResult::NotHandled), 2763 m_delegate_sp() {} 2764 2765 // Menuitem constructor 2766 Menu::Menu(const char *name, const char *key_name, int key_value, 2767 uint64_t identifier) 2768 : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid), 2769 m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0), 2770 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr), 2771 m_submenus(), m_canned_result(MenuActionResult::NotHandled), 2772 m_delegate_sp() { 2773 if (name && name[0]) { 2774 m_name = name; 2775 m_type = Type::Item; 2776 if (key_name && key_name[0]) 2777 m_key_name = key_name; 2778 } else { 2779 m_type = Type::Separator; 2780 } 2781 } 2782 2783 void Menu::RecalculateNameLengths() { 2784 m_max_submenu_name_length = 0; 2785 m_max_submenu_key_name_length = 0; 2786 Menus &submenus = GetSubmenus(); 2787 const size_t num_submenus = submenus.size(); 2788 for (size_t i = 0; i < num_submenus; ++i) { 2789 Menu *submenu = submenus[i].get(); 2790 if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size()) 2791 m_max_submenu_name_length = submenu->m_name.size(); 2792 if (static_cast<size_t>(m_max_submenu_key_name_length) < 2793 submenu->m_key_name.size()) 2794 m_max_submenu_key_name_length = submenu->m_key_name.size(); 2795 } 2796 } 2797 2798 void Menu::AddSubmenu(const MenuSP &menu_sp) { 2799 menu_sp->m_parent = this; 2800 if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size()) 2801 m_max_submenu_name_length = menu_sp->m_name.size(); 2802 if (static_cast<size_t>(m_max_submenu_key_name_length) < 2803 menu_sp->m_key_name.size()) 2804 m_max_submenu_key_name_length = menu_sp->m_key_name.size(); 2805 m_submenus.push_back(menu_sp); 2806 } 2807 2808 void Menu::DrawMenuTitle(Window &window, bool highlight) { 2809 if (m_type == Type::Separator) { 2810 window.MoveCursor(0, window.GetCursorY()); 2811 window.PutChar(ACS_LTEE); 2812 int width = window.GetWidth(); 2813 if (width > 2) { 2814 width -= 2; 2815 for (int i = 0; i < width; ++i) 2816 window.PutChar(ACS_HLINE); 2817 } 2818 window.PutChar(ACS_RTEE); 2819 } else { 2820 const int shortcut_key = m_key_value; 2821 bool underlined_shortcut = false; 2822 const attr_t highlight_attr = A_REVERSE; 2823 if (highlight) 2824 window.AttributeOn(highlight_attr); 2825 if (llvm::isPrint(shortcut_key)) { 2826 size_t lower_pos = m_name.find(tolower(shortcut_key)); 2827 size_t upper_pos = m_name.find(toupper(shortcut_key)); 2828 const char *name = m_name.c_str(); 2829 size_t pos = std::min<size_t>(lower_pos, upper_pos); 2830 if (pos != std::string::npos) { 2831 underlined_shortcut = true; 2832 if (pos > 0) { 2833 window.PutCString(name, pos); 2834 name += pos; 2835 } 2836 const attr_t shortcut_attr = A_UNDERLINE | A_BOLD; 2837 window.AttributeOn(shortcut_attr); 2838 window.PutChar(name[0]); 2839 window.AttributeOff(shortcut_attr); 2840 name++; 2841 if (name[0]) 2842 window.PutCString(name); 2843 } 2844 } 2845 2846 if (!underlined_shortcut) { 2847 window.PutCString(m_name.c_str()); 2848 } 2849 2850 if (highlight) 2851 window.AttributeOff(highlight_attr); 2852 2853 if (m_key_name.empty()) { 2854 if (!underlined_shortcut && llvm::isPrint(m_key_value)) { 2855 window.AttributeOn(COLOR_PAIR(MagentaOnWhite)); 2856 window.Printf(" (%c)", m_key_value); 2857 window.AttributeOff(COLOR_PAIR(MagentaOnWhite)); 2858 } 2859 } else { 2860 window.AttributeOn(COLOR_PAIR(MagentaOnWhite)); 2861 window.Printf(" (%s)", m_key_name.c_str()); 2862 window.AttributeOff(COLOR_PAIR(MagentaOnWhite)); 2863 } 2864 } 2865 } 2866 2867 bool Menu::WindowDelegateDraw(Window &window, bool force) { 2868 Menus &submenus = GetSubmenus(); 2869 const size_t num_submenus = submenus.size(); 2870 const int selected_idx = GetSelectedSubmenuIndex(); 2871 Menu::Type menu_type = GetType(); 2872 switch (menu_type) { 2873 case Menu::Type::Bar: { 2874 window.SetBackground(BlackOnWhite); 2875 window.MoveCursor(0, 0); 2876 for (size_t i = 0; i < num_submenus; ++i) { 2877 Menu *menu = submenus[i].get(); 2878 if (i > 0) 2879 window.PutChar(' '); 2880 menu->SetStartingColumn(window.GetCursorX()); 2881 window.PutCString("| "); 2882 menu->DrawMenuTitle(window, false); 2883 } 2884 window.PutCString(" |"); 2885 } break; 2886 2887 case Menu::Type::Item: { 2888 int y = 1; 2889 int x = 3; 2890 // Draw the menu 2891 int cursor_x = 0; 2892 int cursor_y = 0; 2893 window.Erase(); 2894 window.SetBackground(BlackOnWhite); 2895 window.Box(); 2896 for (size_t i = 0; i < num_submenus; ++i) { 2897 const bool is_selected = (i == static_cast<size_t>(selected_idx)); 2898 window.MoveCursor(x, y + i); 2899 if (is_selected) { 2900 // Remember where we want the cursor to be 2901 cursor_x = x - 1; 2902 cursor_y = y + i; 2903 } 2904 submenus[i]->DrawMenuTitle(window, is_selected); 2905 } 2906 window.MoveCursor(cursor_x, cursor_y); 2907 } break; 2908 2909 default: 2910 case Menu::Type::Separator: 2911 break; 2912 } 2913 return true; // Drawing handled... 2914 } 2915 2916 HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) { 2917 HandleCharResult result = eKeyNotHandled; 2918 2919 Menus &submenus = GetSubmenus(); 2920 const size_t num_submenus = submenus.size(); 2921 const int selected_idx = GetSelectedSubmenuIndex(); 2922 Menu::Type menu_type = GetType(); 2923 if (menu_type == Menu::Type::Bar) { 2924 MenuSP run_menu_sp; 2925 switch (key) { 2926 case KEY_DOWN: 2927 case KEY_UP: 2928 // Show last menu or first menu 2929 if (selected_idx < static_cast<int>(num_submenus)) 2930 run_menu_sp = submenus[selected_idx]; 2931 else if (!submenus.empty()) 2932 run_menu_sp = submenus.front(); 2933 result = eKeyHandled; 2934 break; 2935 2936 case KEY_RIGHT: 2937 ++m_selected; 2938 if (m_selected >= static_cast<int>(num_submenus)) 2939 m_selected = 0; 2940 if (m_selected < static_cast<int>(num_submenus)) 2941 run_menu_sp = submenus[m_selected]; 2942 else if (!submenus.empty()) 2943 run_menu_sp = submenus.front(); 2944 result = eKeyHandled; 2945 break; 2946 2947 case KEY_LEFT: 2948 --m_selected; 2949 if (m_selected < 0) 2950 m_selected = num_submenus - 1; 2951 if (m_selected < static_cast<int>(num_submenus)) 2952 run_menu_sp = submenus[m_selected]; 2953 else if (!submenus.empty()) 2954 run_menu_sp = submenus.front(); 2955 result = eKeyHandled; 2956 break; 2957 2958 default: 2959 for (size_t i = 0; i < num_submenus; ++i) { 2960 if (submenus[i]->GetKeyValue() == key) { 2961 SetSelectedSubmenuIndex(i); 2962 run_menu_sp = submenus[i]; 2963 result = eKeyHandled; 2964 break; 2965 } 2966 } 2967 break; 2968 } 2969 2970 if (run_menu_sp) { 2971 // Run the action on this menu in case we need to populate the menu with 2972 // dynamic content and also in case check marks, and any other menu 2973 // decorations need to be calculated 2974 if (run_menu_sp->Action() == MenuActionResult::Quit) 2975 return eQuitApplication; 2976 2977 Rect menu_bounds; 2978 menu_bounds.origin.x = run_menu_sp->GetStartingColumn(); 2979 menu_bounds.origin.y = 1; 2980 menu_bounds.size.width = run_menu_sp->GetDrawWidth(); 2981 menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2; 2982 if (m_menu_window_sp) 2983 window.GetParent()->RemoveSubWindow(m_menu_window_sp.get()); 2984 2985 m_menu_window_sp = window.GetParent()->CreateSubWindow( 2986 run_menu_sp->GetName().c_str(), menu_bounds, true); 2987 m_menu_window_sp->SetDelegate(run_menu_sp); 2988 } 2989 } else if (menu_type == Menu::Type::Item) { 2990 switch (key) { 2991 case KEY_DOWN: 2992 if (m_submenus.size() > 1) { 2993 const int start_select = m_selected; 2994 while (++m_selected != start_select) { 2995 if (static_cast<size_t>(m_selected) >= num_submenus) 2996 m_selected = 0; 2997 if (m_submenus[m_selected]->GetType() == Type::Separator) 2998 continue; 2999 else 3000 break; 3001 } 3002 return eKeyHandled; 3003 } 3004 break; 3005 3006 case KEY_UP: 3007 if (m_submenus.size() > 1) { 3008 const int start_select = m_selected; 3009 while (--m_selected != start_select) { 3010 if (m_selected < static_cast<int>(0)) 3011 m_selected = num_submenus - 1; 3012 if (m_submenus[m_selected]->GetType() == Type::Separator) 3013 continue; 3014 else 3015 break; 3016 } 3017 return eKeyHandled; 3018 } 3019 break; 3020 3021 case KEY_RETURN: 3022 if (static_cast<size_t>(selected_idx) < num_submenus) { 3023 if (submenus[selected_idx]->Action() == MenuActionResult::Quit) 3024 return eQuitApplication; 3025 window.GetParent()->RemoveSubWindow(&window); 3026 return eKeyHandled; 3027 } 3028 break; 3029 3030 case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in 3031 // case other chars are entered for escaped sequences 3032 window.GetParent()->RemoveSubWindow(&window); 3033 return eKeyHandled; 3034 3035 default: 3036 for (size_t i = 0; i < num_submenus; ++i) { 3037 Menu *menu = submenus[i].get(); 3038 if (menu->GetKeyValue() == key) { 3039 SetSelectedSubmenuIndex(i); 3040 window.GetParent()->RemoveSubWindow(&window); 3041 if (menu->Action() == MenuActionResult::Quit) 3042 return eQuitApplication; 3043 return eKeyHandled; 3044 } 3045 } 3046 break; 3047 } 3048 } else if (menu_type == Menu::Type::Separator) { 3049 } 3050 return result; 3051 } 3052 3053 class Application { 3054 public: 3055 Application(FILE *in, FILE *out) 3056 : m_window_sp(), m_screen(nullptr), m_in(in), m_out(out) {} 3057 3058 ~Application() { 3059 m_window_delegates.clear(); 3060 m_window_sp.reset(); 3061 if (m_screen) { 3062 ::delscreen(m_screen); 3063 m_screen = nullptr; 3064 } 3065 } 3066 3067 void Initialize() { 3068 m_screen = ::newterm(nullptr, m_out, m_in); 3069 ::start_color(); 3070 ::curs_set(0); 3071 ::noecho(); 3072 ::keypad(stdscr, TRUE); 3073 } 3074 3075 void Terminate() { ::endwin(); } 3076 3077 void Run(Debugger &debugger) { 3078 bool done = false; 3079 int delay_in_tenths_of_a_second = 1; 3080 3081 // Alas the threading model in curses is a bit lame so we need to resort to 3082 // polling every 0.5 seconds. We could poll for stdin ourselves and then 3083 // pass the keys down but then we need to translate all of the escape 3084 // sequences ourselves. So we resort to polling for input because we need 3085 // to receive async process events while in this loop. 3086 3087 halfdelay(delay_in_tenths_of_a_second); // Poll using some number of tenths 3088 // of seconds seconds when calling 3089 // Window::GetChar() 3090 3091 ListenerSP listener_sp( 3092 Listener::MakeListener("lldb.IOHandler.curses.Application")); 3093 ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass()); 3094 debugger.EnableForwardEvents(listener_sp); 3095 3096 m_update_screen = true; 3097 #if defined(__APPLE__) 3098 std::deque<int> escape_chars; 3099 #endif 3100 3101 while (!done) { 3102 if (m_update_screen) { 3103 m_window_sp->Draw(false); 3104 // All windows should be calling Window::DeferredRefresh() instead of 3105 // Window::Refresh() so we can do a single update and avoid any screen 3106 // blinking 3107 update_panels(); 3108 3109 // Cursor hiding isn't working on MacOSX, so hide it in the top left 3110 // corner 3111 m_window_sp->MoveCursor(0, 0); 3112 3113 doupdate(); 3114 m_update_screen = false; 3115 } 3116 3117 #if defined(__APPLE__) 3118 // Terminal.app doesn't map its function keys correctly, F1-F4 default 3119 // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if 3120 // possible 3121 int ch; 3122 if (escape_chars.empty()) 3123 ch = m_window_sp->GetChar(); 3124 else { 3125 ch = escape_chars.front(); 3126 escape_chars.pop_front(); 3127 } 3128 if (ch == KEY_ESCAPE) { 3129 int ch2 = m_window_sp->GetChar(); 3130 if (ch2 == 'O') { 3131 int ch3 = m_window_sp->GetChar(); 3132 switch (ch3) { 3133 case 'P': 3134 ch = KEY_F(1); 3135 break; 3136 case 'Q': 3137 ch = KEY_F(2); 3138 break; 3139 case 'R': 3140 ch = KEY_F(3); 3141 break; 3142 case 'S': 3143 ch = KEY_F(4); 3144 break; 3145 default: 3146 escape_chars.push_back(ch2); 3147 if (ch3 != -1) 3148 escape_chars.push_back(ch3); 3149 break; 3150 } 3151 } else if (ch2 != -1) 3152 escape_chars.push_back(ch2); 3153 } 3154 #else 3155 int ch = m_window_sp->GetChar(); 3156 3157 #endif 3158 if (ch == -1) { 3159 if (feof(m_in) || ferror(m_in)) { 3160 done = true; 3161 } else { 3162 // Just a timeout from using halfdelay(), check for events 3163 EventSP event_sp; 3164 while (listener_sp->PeekAtNextEvent()) { 3165 listener_sp->GetEvent(event_sp, std::chrono::seconds(0)); 3166 3167 if (event_sp) { 3168 Broadcaster *broadcaster = event_sp->GetBroadcaster(); 3169 if (broadcaster) { 3170 // uint32_t event_type = event_sp->GetType(); 3171 ConstString broadcaster_class( 3172 broadcaster->GetBroadcasterClass()); 3173 if (broadcaster_class == broadcaster_class_process) { 3174 m_update_screen = true; 3175 continue; // Don't get any key, just update our view 3176 } 3177 } 3178 } 3179 } 3180 } 3181 } else { 3182 HandleCharResult key_result = m_window_sp->HandleChar(ch); 3183 switch (key_result) { 3184 case eKeyHandled: 3185 m_update_screen = true; 3186 break; 3187 case eKeyNotHandled: 3188 if (ch == 12) { // Ctrl+L, force full redraw 3189 redrawwin(m_window_sp->get()); 3190 m_update_screen = true; 3191 } 3192 break; 3193 case eQuitApplication: 3194 done = true; 3195 break; 3196 } 3197 } 3198 } 3199 3200 debugger.CancelForwardEvents(listener_sp); 3201 } 3202 3203 WindowSP &GetMainWindow() { 3204 if (!m_window_sp) 3205 m_window_sp = std::make_shared<Window>("main", stdscr, false); 3206 return m_window_sp; 3207 } 3208 3209 void TerminalSizeChanged() { 3210 ::endwin(); 3211 ::refresh(); 3212 Rect content_bounds = m_window_sp->GetFrame(); 3213 m_window_sp->SetBounds(content_bounds); 3214 if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar")) 3215 menubar_window_sp->SetBounds(content_bounds.MakeMenuBar()); 3216 if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status")) 3217 status_window_sp->SetBounds(content_bounds.MakeStatusBar()); 3218 3219 WindowSP source_window_sp = m_window_sp->FindSubWindow("Source"); 3220 WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables"); 3221 WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers"); 3222 WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads"); 3223 3224 Rect threads_bounds; 3225 Rect source_variables_bounds; 3226 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, 3227 threads_bounds); 3228 if (threads_window_sp) 3229 threads_window_sp->SetBounds(threads_bounds); 3230 else 3231 source_variables_bounds = content_bounds; 3232 3233 Rect source_bounds; 3234 Rect variables_registers_bounds; 3235 source_variables_bounds.HorizontalSplitPercentage( 3236 0.70, source_bounds, variables_registers_bounds); 3237 if (variables_window_sp || registers_window_sp) { 3238 if (variables_window_sp && registers_window_sp) { 3239 Rect variables_bounds; 3240 Rect registers_bounds; 3241 variables_registers_bounds.VerticalSplitPercentage( 3242 0.50, variables_bounds, registers_bounds); 3243 variables_window_sp->SetBounds(variables_bounds); 3244 registers_window_sp->SetBounds(registers_bounds); 3245 } else if (variables_window_sp) { 3246 variables_window_sp->SetBounds(variables_registers_bounds); 3247 } else { 3248 registers_window_sp->SetBounds(variables_registers_bounds); 3249 } 3250 } else { 3251 source_bounds = source_variables_bounds; 3252 } 3253 3254 source_window_sp->SetBounds(source_bounds); 3255 3256 touchwin(stdscr); 3257 redrawwin(m_window_sp->get()); 3258 m_update_screen = true; 3259 } 3260 3261 protected: 3262 WindowSP m_window_sp; 3263 WindowDelegates m_window_delegates; 3264 SCREEN *m_screen; 3265 FILE *m_in; 3266 FILE *m_out; 3267 bool m_update_screen = false; 3268 }; 3269 3270 } // namespace curses 3271 3272 using namespace curses; 3273 3274 struct Row { 3275 ValueObjectUpdater value; 3276 Row *parent; 3277 // The process stop ID when the children were calculated. 3278 uint32_t children_stop_id = 0; 3279 int row_idx = 0; 3280 int x = 1; 3281 int y = 1; 3282 bool might_have_children; 3283 bool expanded = false; 3284 bool calculated_children = false; 3285 std::vector<Row> children; 3286 3287 Row(const ValueObjectSP &v, Row *p) 3288 : value(v), parent(p), 3289 might_have_children(v ? v->MightHaveChildren() : false) {} 3290 3291 size_t GetDepth() const { 3292 if (parent) 3293 return 1 + parent->GetDepth(); 3294 return 0; 3295 } 3296 3297 void Expand() { expanded = true; } 3298 3299 std::vector<Row> &GetChildren() { 3300 ProcessSP process_sp = value.GetProcessSP(); 3301 auto stop_id = process_sp->GetStopID(); 3302 if (process_sp && stop_id != children_stop_id) { 3303 children_stop_id = stop_id; 3304 calculated_children = false; 3305 } 3306 if (!calculated_children) { 3307 children.clear(); 3308 calculated_children = true; 3309 ValueObjectSP valobj = value.GetSP(); 3310 if (valobj) { 3311 const size_t num_children = valobj->GetNumChildren(); 3312 for (size_t i = 0; i < num_children; ++i) { 3313 children.push_back(Row(valobj->GetChildAtIndex(i, true), this)); 3314 } 3315 } 3316 } 3317 return children; 3318 } 3319 3320 void Unexpand() { 3321 expanded = false; 3322 calculated_children = false; 3323 children.clear(); 3324 } 3325 3326 void DrawTree(Window &window) { 3327 if (parent) 3328 parent->DrawTreeForChild(window, this, 0); 3329 3330 if (might_have_children) { 3331 // It we can get UTF8 characters to work we should try to use the 3332 // "symbol" UTF8 string below 3333 // const char *symbol = ""; 3334 // if (row.expanded) 3335 // symbol = "\xe2\x96\xbd "; 3336 // else 3337 // symbol = "\xe2\x96\xb7 "; 3338 // window.PutCString (symbol); 3339 3340 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v' 3341 // or '>' character... 3342 // if (expanded) 3343 // window.PutChar (ACS_DARROW); 3344 // else 3345 // window.PutChar (ACS_RARROW); 3346 // Since we can't find any good looking right arrow/down arrow symbols, 3347 // just use a diamond... 3348 window.PutChar(ACS_DIAMOND); 3349 window.PutChar(ACS_HLINE); 3350 } 3351 } 3352 3353 void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) { 3354 if (parent) 3355 parent->DrawTreeForChild(window, this, reverse_depth + 1); 3356 3357 if (&GetChildren().back() == child) { 3358 // Last child 3359 if (reverse_depth == 0) { 3360 window.PutChar(ACS_LLCORNER); 3361 window.PutChar(ACS_HLINE); 3362 } else { 3363 window.PutChar(' '); 3364 window.PutChar(' '); 3365 } 3366 } else { 3367 if (reverse_depth == 0) { 3368 window.PutChar(ACS_LTEE); 3369 window.PutChar(ACS_HLINE); 3370 } else { 3371 window.PutChar(ACS_VLINE); 3372 window.PutChar(' '); 3373 } 3374 } 3375 } 3376 }; 3377 3378 struct DisplayOptions { 3379 bool show_types; 3380 }; 3381 3382 class TreeItem; 3383 3384 class TreeDelegate { 3385 public: 3386 TreeDelegate() = default; 3387 virtual ~TreeDelegate() = default; 3388 3389 virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0; 3390 virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0; 3391 virtual void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index, 3392 TreeItem *&selected_item) { 3393 return; 3394 } 3395 virtual bool TreeDelegateItemSelected( 3396 TreeItem &item) = 0; // Return true if we need to update views 3397 virtual bool TreeDelegateExpandRootByDefault() { return false; } 3398 }; 3399 3400 typedef std::shared_ptr<TreeDelegate> TreeDelegateSP; 3401 3402 class TreeItem { 3403 public: 3404 TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children) 3405 : m_parent(parent), m_delegate(delegate), m_user_data(nullptr), 3406 m_identifier(0), m_row_idx(-1), m_children(), 3407 m_might_have_children(might_have_children), m_is_expanded(false) { 3408 if (m_parent == nullptr) 3409 m_is_expanded = m_delegate.TreeDelegateExpandRootByDefault(); 3410 } 3411 3412 TreeItem &operator=(const TreeItem &rhs) { 3413 if (this != &rhs) { 3414 m_parent = rhs.m_parent; 3415 m_delegate = rhs.m_delegate; 3416 m_user_data = rhs.m_user_data; 3417 m_identifier = rhs.m_identifier; 3418 m_row_idx = rhs.m_row_idx; 3419 m_children = rhs.m_children; 3420 m_might_have_children = rhs.m_might_have_children; 3421 m_is_expanded = rhs.m_is_expanded; 3422 } 3423 return *this; 3424 } 3425 3426 TreeItem(const TreeItem &) = default; 3427 3428 size_t GetDepth() const { 3429 if (m_parent) 3430 return 1 + m_parent->GetDepth(); 3431 return 0; 3432 } 3433 3434 int GetRowIndex() const { return m_row_idx; } 3435 3436 void ClearChildren() { m_children.clear(); } 3437 3438 void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); } 3439 3440 TreeItem &operator[](size_t i) { return m_children[i]; } 3441 3442 void SetRowIndex(int row_idx) { m_row_idx = row_idx; } 3443 3444 size_t GetNumChildren() { 3445 m_delegate.TreeDelegateGenerateChildren(*this); 3446 return m_children.size(); 3447 } 3448 3449 void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); } 3450 3451 void CalculateRowIndexes(int &row_idx) { 3452 SetRowIndex(row_idx); 3453 ++row_idx; 3454 3455 const bool expanded = IsExpanded(); 3456 3457 // The root item must calculate its children, or we must calculate the 3458 // number of children if the item is expanded 3459 if (m_parent == nullptr || expanded) 3460 GetNumChildren(); 3461 3462 for (auto &item : m_children) { 3463 if (expanded) 3464 item.CalculateRowIndexes(row_idx); 3465 else 3466 item.SetRowIndex(-1); 3467 } 3468 } 3469 3470 TreeItem *GetParent() { return m_parent; } 3471 3472 bool IsExpanded() const { return m_is_expanded; } 3473 3474 void Expand() { m_is_expanded = true; } 3475 3476 void Unexpand() { m_is_expanded = false; } 3477 3478 bool Draw(Window &window, const int first_visible_row, 3479 const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) { 3480 if (num_rows_left <= 0) 3481 return false; 3482 3483 if (m_row_idx >= first_visible_row) { 3484 window.MoveCursor(2, row_idx + 1); 3485 3486 if (m_parent) 3487 m_parent->DrawTreeForChild(window, this, 0); 3488 3489 if (m_might_have_children) { 3490 // It we can get UTF8 characters to work we should try to use the 3491 // "symbol" UTF8 string below 3492 // const char *symbol = ""; 3493 // if (row.expanded) 3494 // symbol = "\xe2\x96\xbd "; 3495 // else 3496 // symbol = "\xe2\x96\xb7 "; 3497 // window.PutCString (symbol); 3498 3499 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 3500 // 'v' or '>' character... 3501 // if (expanded) 3502 // window.PutChar (ACS_DARROW); 3503 // else 3504 // window.PutChar (ACS_RARROW); 3505 // Since we can't find any good looking right arrow/down arrow symbols, 3506 // just use a diamond... 3507 window.PutChar(ACS_DIAMOND); 3508 window.PutChar(ACS_HLINE); 3509 } 3510 bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) && 3511 window.IsActive(); 3512 3513 if (highlight) 3514 window.AttributeOn(A_REVERSE); 3515 3516 m_delegate.TreeDelegateDrawTreeItem(*this, window); 3517 3518 if (highlight) 3519 window.AttributeOff(A_REVERSE); 3520 ++row_idx; 3521 --num_rows_left; 3522 } 3523 3524 if (num_rows_left <= 0) 3525 return false; // We are done drawing... 3526 3527 if (IsExpanded()) { 3528 for (auto &item : m_children) { 3529 // If we displayed all the rows and item.Draw() returns false we are 3530 // done drawing and can exit this for loop 3531 if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx, 3532 num_rows_left)) 3533 break; 3534 } 3535 } 3536 return num_rows_left >= 0; // Return true if not done drawing yet 3537 } 3538 3539 void DrawTreeForChild(Window &window, TreeItem *child, 3540 uint32_t reverse_depth) { 3541 if (m_parent) 3542 m_parent->DrawTreeForChild(window, this, reverse_depth + 1); 3543 3544 if (&m_children.back() == child) { 3545 // Last child 3546 if (reverse_depth == 0) { 3547 window.PutChar(ACS_LLCORNER); 3548 window.PutChar(ACS_HLINE); 3549 } else { 3550 window.PutChar(' '); 3551 window.PutChar(' '); 3552 } 3553 } else { 3554 if (reverse_depth == 0) { 3555 window.PutChar(ACS_LTEE); 3556 window.PutChar(ACS_HLINE); 3557 } else { 3558 window.PutChar(ACS_VLINE); 3559 window.PutChar(' '); 3560 } 3561 } 3562 } 3563 3564 TreeItem *GetItemForRowIndex(uint32_t row_idx) { 3565 if (static_cast<uint32_t>(m_row_idx) == row_idx) 3566 return this; 3567 if (m_children.empty()) 3568 return nullptr; 3569 if (IsExpanded()) { 3570 for (auto &item : m_children) { 3571 TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx); 3572 if (selected_item_ptr) 3573 return selected_item_ptr; 3574 } 3575 } 3576 return nullptr; 3577 } 3578 3579 void *GetUserData() const { return m_user_data; } 3580 3581 void SetUserData(void *user_data) { m_user_data = user_data; } 3582 3583 uint64_t GetIdentifier() const { return m_identifier; } 3584 3585 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } 3586 3587 void SetMightHaveChildren(bool b) { m_might_have_children = b; } 3588 3589 protected: 3590 TreeItem *m_parent; 3591 TreeDelegate &m_delegate; 3592 void *m_user_data; 3593 uint64_t m_identifier; 3594 int m_row_idx; // Zero based visible row index, -1 if not visible or for the 3595 // root item 3596 std::vector<TreeItem> m_children; 3597 bool m_might_have_children; 3598 bool m_is_expanded; 3599 }; 3600 3601 class TreeWindowDelegate : public WindowDelegate { 3602 public: 3603 TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp) 3604 : m_debugger(debugger), m_delegate_sp(delegate_sp), 3605 m_root(nullptr, *delegate_sp, true), m_selected_item(nullptr), 3606 m_num_rows(0), m_selected_row_idx(0), m_first_visible_row(0), 3607 m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {} 3608 3609 int NumVisibleRows() const { return m_max_y - m_min_y; } 3610 3611 bool WindowDelegateDraw(Window &window, bool force) override { 3612 ExecutionContext exe_ctx( 3613 m_debugger.GetCommandInterpreter().GetExecutionContext()); 3614 Process *process = exe_ctx.GetProcessPtr(); 3615 3616 bool display_content = false; 3617 if (process) { 3618 StateType state = process->GetState(); 3619 if (StateIsStoppedState(state, true)) { 3620 // We are stopped, so it is ok to 3621 display_content = true; 3622 } else if (StateIsRunningState(state)) { 3623 return true; // Don't do any updating when we are running 3624 } 3625 } 3626 3627 m_min_x = 2; 3628 m_min_y = 1; 3629 m_max_x = window.GetWidth() - 1; 3630 m_max_y = window.GetHeight() - 1; 3631 3632 window.Erase(); 3633 window.DrawTitleBox(window.GetName()); 3634 3635 if (display_content) { 3636 const int num_visible_rows = NumVisibleRows(); 3637 m_num_rows = 0; 3638 m_root.CalculateRowIndexes(m_num_rows); 3639 m_delegate_sp->TreeDelegateUpdateSelection(m_root, m_selected_row_idx, 3640 m_selected_item); 3641 3642 // If we unexpanded while having something selected our total number of 3643 // rows is less than the num visible rows, then make sure we show all the 3644 // rows by setting the first visible row accordingly. 3645 if (m_first_visible_row > 0 && m_num_rows < num_visible_rows) 3646 m_first_visible_row = 0; 3647 3648 // Make sure the selected row is always visible 3649 if (m_selected_row_idx < m_first_visible_row) 3650 m_first_visible_row = m_selected_row_idx; 3651 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) 3652 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; 3653 3654 int row_idx = 0; 3655 int num_rows_left = num_visible_rows; 3656 m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx, 3657 num_rows_left); 3658 // Get the selected row 3659 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 3660 } else { 3661 m_selected_item = nullptr; 3662 } 3663 3664 return true; // Drawing handled 3665 } 3666 3667 const char *WindowDelegateGetHelpText() override { 3668 return "Thread window keyboard shortcuts:"; 3669 } 3670 3671 KeyHelp *WindowDelegateGetKeyHelp() override { 3672 static curses::KeyHelp g_source_view_key_help[] = { 3673 {KEY_UP, "Select previous item"}, 3674 {KEY_DOWN, "Select next item"}, 3675 {KEY_RIGHT, "Expand the selected item"}, 3676 {KEY_LEFT, 3677 "Unexpand the selected item or select parent if not expanded"}, 3678 {KEY_PPAGE, "Page up"}, 3679 {KEY_NPAGE, "Page down"}, 3680 {'h', "Show help dialog"}, 3681 {' ', "Toggle item expansion"}, 3682 {',', "Page up"}, 3683 {'.', "Page down"}, 3684 {'\0', nullptr}}; 3685 return g_source_view_key_help; 3686 } 3687 3688 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 3689 switch (c) { 3690 case ',': 3691 case KEY_PPAGE: 3692 // Page up key 3693 if (m_first_visible_row > 0) { 3694 if (m_first_visible_row > m_max_y) 3695 m_first_visible_row -= m_max_y; 3696 else 3697 m_first_visible_row = 0; 3698 m_selected_row_idx = m_first_visible_row; 3699 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 3700 if (m_selected_item) 3701 m_selected_item->ItemWasSelected(); 3702 } 3703 return eKeyHandled; 3704 3705 case '.': 3706 case KEY_NPAGE: 3707 // Page down key 3708 if (m_num_rows > m_max_y) { 3709 if (m_first_visible_row + m_max_y < m_num_rows) { 3710 m_first_visible_row += m_max_y; 3711 m_selected_row_idx = m_first_visible_row; 3712 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 3713 if (m_selected_item) 3714 m_selected_item->ItemWasSelected(); 3715 } 3716 } 3717 return eKeyHandled; 3718 3719 case KEY_UP: 3720 if (m_selected_row_idx > 0) { 3721 --m_selected_row_idx; 3722 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 3723 if (m_selected_item) 3724 m_selected_item->ItemWasSelected(); 3725 } 3726 return eKeyHandled; 3727 3728 case KEY_DOWN: 3729 if (m_selected_row_idx + 1 < m_num_rows) { 3730 ++m_selected_row_idx; 3731 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 3732 if (m_selected_item) 3733 m_selected_item->ItemWasSelected(); 3734 } 3735 return eKeyHandled; 3736 3737 case KEY_RIGHT: 3738 if (m_selected_item) { 3739 if (!m_selected_item->IsExpanded()) 3740 m_selected_item->Expand(); 3741 } 3742 return eKeyHandled; 3743 3744 case KEY_LEFT: 3745 if (m_selected_item) { 3746 if (m_selected_item->IsExpanded()) 3747 m_selected_item->Unexpand(); 3748 else if (m_selected_item->GetParent()) { 3749 m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex(); 3750 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 3751 if (m_selected_item) 3752 m_selected_item->ItemWasSelected(); 3753 } 3754 } 3755 return eKeyHandled; 3756 3757 case ' ': 3758 // Toggle expansion state when SPACE is pressed 3759 if (m_selected_item) { 3760 if (m_selected_item->IsExpanded()) 3761 m_selected_item->Unexpand(); 3762 else 3763 m_selected_item->Expand(); 3764 } 3765 return eKeyHandled; 3766 3767 case 'h': 3768 window.CreateHelpSubwindow(); 3769 return eKeyHandled; 3770 3771 default: 3772 break; 3773 } 3774 return eKeyNotHandled; 3775 } 3776 3777 protected: 3778 Debugger &m_debugger; 3779 TreeDelegateSP m_delegate_sp; 3780 TreeItem m_root; 3781 TreeItem *m_selected_item; 3782 int m_num_rows; 3783 int m_selected_row_idx; 3784 int m_first_visible_row; 3785 int m_min_x; 3786 int m_min_y; 3787 int m_max_x; 3788 int m_max_y; 3789 }; 3790 3791 class FrameTreeDelegate : public TreeDelegate { 3792 public: 3793 FrameTreeDelegate() : TreeDelegate() { 3794 FormatEntity::Parse( 3795 "frame #${frame.index}: {${function.name}${function.pc-offset}}}", 3796 m_format); 3797 } 3798 3799 ~FrameTreeDelegate() override = default; 3800 3801 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 3802 Thread *thread = (Thread *)item.GetUserData(); 3803 if (thread) { 3804 const uint64_t frame_idx = item.GetIdentifier(); 3805 StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx); 3806 if (frame_sp) { 3807 StreamString strm; 3808 const SymbolContext &sc = 3809 frame_sp->GetSymbolContext(eSymbolContextEverything); 3810 ExecutionContext exe_ctx(frame_sp); 3811 if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr, 3812 nullptr, false, false)) { 3813 int right_pad = 1; 3814 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 3815 } 3816 } 3817 } 3818 } 3819 3820 void TreeDelegateGenerateChildren(TreeItem &item) override { 3821 // No children for frames yet... 3822 } 3823 3824 bool TreeDelegateItemSelected(TreeItem &item) override { 3825 Thread *thread = (Thread *)item.GetUserData(); 3826 if (thread) { 3827 thread->GetProcess()->GetThreadList().SetSelectedThreadByID( 3828 thread->GetID()); 3829 const uint64_t frame_idx = item.GetIdentifier(); 3830 thread->SetSelectedFrameByIndex(frame_idx); 3831 return true; 3832 } 3833 return false; 3834 } 3835 3836 protected: 3837 FormatEntity::Entry m_format; 3838 }; 3839 3840 class ThreadTreeDelegate : public TreeDelegate { 3841 public: 3842 ThreadTreeDelegate(Debugger &debugger) 3843 : TreeDelegate(), m_debugger(debugger), m_tid(LLDB_INVALID_THREAD_ID), 3844 m_stop_id(UINT32_MAX) { 3845 FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop " 3846 "reason = ${thread.stop-reason}}", 3847 m_format); 3848 } 3849 3850 ~ThreadTreeDelegate() override = default; 3851 3852 ProcessSP GetProcess() { 3853 return m_debugger.GetCommandInterpreter() 3854 .GetExecutionContext() 3855 .GetProcessSP(); 3856 } 3857 3858 ThreadSP GetThread(const TreeItem &item) { 3859 ProcessSP process_sp = GetProcess(); 3860 if (process_sp) 3861 return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier()); 3862 return ThreadSP(); 3863 } 3864 3865 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 3866 ThreadSP thread_sp = GetThread(item); 3867 if (thread_sp) { 3868 StreamString strm; 3869 ExecutionContext exe_ctx(thread_sp); 3870 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, 3871 nullptr, false, false)) { 3872 int right_pad = 1; 3873 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 3874 } 3875 } 3876 } 3877 3878 void TreeDelegateGenerateChildren(TreeItem &item) override { 3879 ProcessSP process_sp = GetProcess(); 3880 if (process_sp && process_sp->IsAlive()) { 3881 StateType state = process_sp->GetState(); 3882 if (StateIsStoppedState(state, true)) { 3883 ThreadSP thread_sp = GetThread(item); 3884 if (thread_sp) { 3885 if (m_stop_id == process_sp->GetStopID() && 3886 thread_sp->GetID() == m_tid) 3887 return; // Children are already up to date 3888 if (!m_frame_delegate_sp) { 3889 // Always expand the thread item the first time we show it 3890 m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>(); 3891 } 3892 3893 m_stop_id = process_sp->GetStopID(); 3894 m_tid = thread_sp->GetID(); 3895 3896 TreeItem t(&item, *m_frame_delegate_sp, false); 3897 size_t num_frames = thread_sp->GetStackFrameCount(); 3898 item.Resize(num_frames, t); 3899 for (size_t i = 0; i < num_frames; ++i) { 3900 item[i].SetUserData(thread_sp.get()); 3901 item[i].SetIdentifier(i); 3902 } 3903 } 3904 return; 3905 } 3906 } 3907 item.ClearChildren(); 3908 } 3909 3910 bool TreeDelegateItemSelected(TreeItem &item) override { 3911 ProcessSP process_sp = GetProcess(); 3912 if (process_sp && process_sp->IsAlive()) { 3913 StateType state = process_sp->GetState(); 3914 if (StateIsStoppedState(state, true)) { 3915 ThreadSP thread_sp = GetThread(item); 3916 if (thread_sp) { 3917 ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList(); 3918 std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex()); 3919 ThreadSP selected_thread_sp = thread_list.GetSelectedThread(); 3920 if (selected_thread_sp->GetID() != thread_sp->GetID()) { 3921 thread_list.SetSelectedThreadByID(thread_sp->GetID()); 3922 return true; 3923 } 3924 } 3925 } 3926 } 3927 return false; 3928 } 3929 3930 protected: 3931 Debugger &m_debugger; 3932 std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp; 3933 lldb::user_id_t m_tid; 3934 uint32_t m_stop_id; 3935 FormatEntity::Entry m_format; 3936 }; 3937 3938 class ThreadsTreeDelegate : public TreeDelegate { 3939 public: 3940 ThreadsTreeDelegate(Debugger &debugger) 3941 : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger), 3942 m_stop_id(UINT32_MAX), m_update_selection(false) { 3943 FormatEntity::Parse("process ${process.id}{, name = ${process.name}}", 3944 m_format); 3945 } 3946 3947 ~ThreadsTreeDelegate() override = default; 3948 3949 ProcessSP GetProcess() { 3950 return m_debugger.GetCommandInterpreter() 3951 .GetExecutionContext() 3952 .GetProcessSP(); 3953 } 3954 3955 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 3956 ProcessSP process_sp = GetProcess(); 3957 if (process_sp && process_sp->IsAlive()) { 3958 StreamString strm; 3959 ExecutionContext exe_ctx(process_sp); 3960 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, 3961 nullptr, false, false)) { 3962 int right_pad = 1; 3963 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 3964 } 3965 } 3966 } 3967 3968 void TreeDelegateGenerateChildren(TreeItem &item) override { 3969 ProcessSP process_sp = GetProcess(); 3970 m_update_selection = false; 3971 if (process_sp && process_sp->IsAlive()) { 3972 StateType state = process_sp->GetState(); 3973 if (StateIsStoppedState(state, true)) { 3974 const uint32_t stop_id = process_sp->GetStopID(); 3975 if (m_stop_id == stop_id) 3976 return; // Children are already up to date 3977 3978 m_stop_id = stop_id; 3979 m_update_selection = true; 3980 3981 if (!m_thread_delegate_sp) { 3982 // Always expand the thread item the first time we show it 3983 // item.Expand(); 3984 m_thread_delegate_sp = 3985 std::make_shared<ThreadTreeDelegate>(m_debugger); 3986 } 3987 3988 TreeItem t(&item, *m_thread_delegate_sp, false); 3989 ThreadList &threads = process_sp->GetThreadList(); 3990 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 3991 ThreadSP selected_thread = threads.GetSelectedThread(); 3992 size_t num_threads = threads.GetSize(); 3993 item.Resize(num_threads, t); 3994 for (size_t i = 0; i < num_threads; ++i) { 3995 ThreadSP thread = threads.GetThreadAtIndex(i); 3996 item[i].SetIdentifier(thread->GetID()); 3997 item[i].SetMightHaveChildren(true); 3998 if (selected_thread->GetID() == thread->GetID()) 3999 item[i].Expand(); 4000 } 4001 return; 4002 } 4003 } 4004 item.ClearChildren(); 4005 } 4006 4007 void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index, 4008 TreeItem *&selected_item) override { 4009 if (!m_update_selection) 4010 return; 4011 4012 ProcessSP process_sp = GetProcess(); 4013 if (!(process_sp && process_sp->IsAlive())) 4014 return; 4015 4016 StateType state = process_sp->GetState(); 4017 if (!StateIsStoppedState(state, true)) 4018 return; 4019 4020 ThreadList &threads = process_sp->GetThreadList(); 4021 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 4022 ThreadSP selected_thread = threads.GetSelectedThread(); 4023 size_t num_threads = threads.GetSize(); 4024 for (size_t i = 0; i < num_threads; ++i) { 4025 ThreadSP thread = threads.GetThreadAtIndex(i); 4026 if (selected_thread->GetID() == thread->GetID()) { 4027 selected_item = &root[i][thread->GetSelectedFrameIndex()]; 4028 selection_index = selected_item->GetRowIndex(); 4029 return; 4030 } 4031 } 4032 } 4033 4034 bool TreeDelegateItemSelected(TreeItem &item) override { return false; } 4035 4036 bool TreeDelegateExpandRootByDefault() override { return true; } 4037 4038 protected: 4039 std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp; 4040 Debugger &m_debugger; 4041 uint32_t m_stop_id; 4042 bool m_update_selection; 4043 FormatEntity::Entry m_format; 4044 }; 4045 4046 class ValueObjectListDelegate : public WindowDelegate { 4047 public: 4048 ValueObjectListDelegate() : m_rows() {} 4049 4050 ValueObjectListDelegate(ValueObjectList &valobj_list) 4051 : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0), 4052 m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) { 4053 SetValues(valobj_list); 4054 } 4055 4056 ~ValueObjectListDelegate() override = default; 4057 4058 void SetValues(ValueObjectList &valobj_list) { 4059 m_selected_row = nullptr; 4060 m_selected_row_idx = 0; 4061 m_first_visible_row = 0; 4062 m_num_rows = 0; 4063 m_rows.clear(); 4064 for (auto &valobj_sp : valobj_list.GetObjects()) 4065 m_rows.push_back(Row(valobj_sp, nullptr)); 4066 } 4067 4068 bool WindowDelegateDraw(Window &window, bool force) override { 4069 m_num_rows = 0; 4070 m_min_x = 2; 4071 m_min_y = 1; 4072 m_max_x = window.GetWidth() - 1; 4073 m_max_y = window.GetHeight() - 1; 4074 4075 window.Erase(); 4076 window.DrawTitleBox(window.GetName()); 4077 4078 const int num_visible_rows = NumVisibleRows(); 4079 const int num_rows = CalculateTotalNumberRows(m_rows); 4080 4081 // If we unexpanded while having something selected our total number of 4082 // rows is less than the num visible rows, then make sure we show all the 4083 // rows by setting the first visible row accordingly. 4084 if (m_first_visible_row > 0 && num_rows < num_visible_rows) 4085 m_first_visible_row = 0; 4086 4087 // Make sure the selected row is always visible 4088 if (m_selected_row_idx < m_first_visible_row) 4089 m_first_visible_row = m_selected_row_idx; 4090 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) 4091 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; 4092 4093 DisplayRows(window, m_rows, g_options); 4094 4095 // Get the selected row 4096 m_selected_row = GetRowForRowIndex(m_selected_row_idx); 4097 // Keep the cursor on the selected row so the highlight and the cursor are 4098 // always on the same line 4099 if (m_selected_row) 4100 window.MoveCursor(m_selected_row->x, m_selected_row->y); 4101 4102 return true; // Drawing handled 4103 } 4104 4105 KeyHelp *WindowDelegateGetKeyHelp() override { 4106 static curses::KeyHelp g_source_view_key_help[] = { 4107 {KEY_UP, "Select previous item"}, 4108 {KEY_DOWN, "Select next item"}, 4109 {KEY_RIGHT, "Expand selected item"}, 4110 {KEY_LEFT, "Unexpand selected item or select parent if not expanded"}, 4111 {KEY_PPAGE, "Page up"}, 4112 {KEY_NPAGE, "Page down"}, 4113 {'A', "Format as annotated address"}, 4114 {'b', "Format as binary"}, 4115 {'B', "Format as hex bytes with ASCII"}, 4116 {'c', "Format as character"}, 4117 {'d', "Format as a signed integer"}, 4118 {'D', "Format selected value using the default format for the type"}, 4119 {'f', "Format as float"}, 4120 {'h', "Show help dialog"}, 4121 {'i', "Format as instructions"}, 4122 {'o', "Format as octal"}, 4123 {'p', "Format as pointer"}, 4124 {'s', "Format as C string"}, 4125 {'t', "Toggle showing/hiding type names"}, 4126 {'u', "Format as an unsigned integer"}, 4127 {'x', "Format as hex"}, 4128 {'X', "Format as uppercase hex"}, 4129 {' ', "Toggle item expansion"}, 4130 {',', "Page up"}, 4131 {'.', "Page down"}, 4132 {'\0', nullptr}}; 4133 return g_source_view_key_help; 4134 } 4135 4136 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 4137 switch (c) { 4138 case 'x': 4139 case 'X': 4140 case 'o': 4141 case 's': 4142 case 'u': 4143 case 'd': 4144 case 'D': 4145 case 'i': 4146 case 'A': 4147 case 'p': 4148 case 'c': 4149 case 'b': 4150 case 'B': 4151 case 'f': 4152 // Change the format for the currently selected item 4153 if (m_selected_row) { 4154 auto valobj_sp = m_selected_row->value.GetSP(); 4155 if (valobj_sp) 4156 valobj_sp->SetFormat(FormatForChar(c)); 4157 } 4158 return eKeyHandled; 4159 4160 case 't': 4161 // Toggle showing type names 4162 g_options.show_types = !g_options.show_types; 4163 return eKeyHandled; 4164 4165 case ',': 4166 case KEY_PPAGE: 4167 // Page up key 4168 if (m_first_visible_row > 0) { 4169 if (static_cast<int>(m_first_visible_row) > m_max_y) 4170 m_first_visible_row -= m_max_y; 4171 else 4172 m_first_visible_row = 0; 4173 m_selected_row_idx = m_first_visible_row; 4174 } 4175 return eKeyHandled; 4176 4177 case '.': 4178 case KEY_NPAGE: 4179 // Page down key 4180 if (m_num_rows > static_cast<size_t>(m_max_y)) { 4181 if (m_first_visible_row + m_max_y < m_num_rows) { 4182 m_first_visible_row += m_max_y; 4183 m_selected_row_idx = m_first_visible_row; 4184 } 4185 } 4186 return eKeyHandled; 4187 4188 case KEY_UP: 4189 if (m_selected_row_idx > 0) 4190 --m_selected_row_idx; 4191 return eKeyHandled; 4192 4193 case KEY_DOWN: 4194 if (m_selected_row_idx + 1 < m_num_rows) 4195 ++m_selected_row_idx; 4196 return eKeyHandled; 4197 4198 case KEY_RIGHT: 4199 if (m_selected_row) { 4200 if (!m_selected_row->expanded) 4201 m_selected_row->Expand(); 4202 } 4203 return eKeyHandled; 4204 4205 case KEY_LEFT: 4206 if (m_selected_row) { 4207 if (m_selected_row->expanded) 4208 m_selected_row->Unexpand(); 4209 else if (m_selected_row->parent) 4210 m_selected_row_idx = m_selected_row->parent->row_idx; 4211 } 4212 return eKeyHandled; 4213 4214 case ' ': 4215 // Toggle expansion state when SPACE is pressed 4216 if (m_selected_row) { 4217 if (m_selected_row->expanded) 4218 m_selected_row->Unexpand(); 4219 else 4220 m_selected_row->Expand(); 4221 } 4222 return eKeyHandled; 4223 4224 case 'h': 4225 window.CreateHelpSubwindow(); 4226 return eKeyHandled; 4227 4228 default: 4229 break; 4230 } 4231 return eKeyNotHandled; 4232 } 4233 4234 protected: 4235 std::vector<Row> m_rows; 4236 Row *m_selected_row = nullptr; 4237 uint32_t m_selected_row_idx = 0; 4238 uint32_t m_first_visible_row = 0; 4239 uint32_t m_num_rows = 0; 4240 int m_min_x; 4241 int m_min_y; 4242 int m_max_x = 0; 4243 int m_max_y = 0; 4244 4245 static Format FormatForChar(int c) { 4246 switch (c) { 4247 case 'x': 4248 return eFormatHex; 4249 case 'X': 4250 return eFormatHexUppercase; 4251 case 'o': 4252 return eFormatOctal; 4253 case 's': 4254 return eFormatCString; 4255 case 'u': 4256 return eFormatUnsigned; 4257 case 'd': 4258 return eFormatDecimal; 4259 case 'D': 4260 return eFormatDefault; 4261 case 'i': 4262 return eFormatInstruction; 4263 case 'A': 4264 return eFormatAddressInfo; 4265 case 'p': 4266 return eFormatPointer; 4267 case 'c': 4268 return eFormatChar; 4269 case 'b': 4270 return eFormatBinary; 4271 case 'B': 4272 return eFormatBytesWithASCII; 4273 case 'f': 4274 return eFormatFloat; 4275 } 4276 return eFormatDefault; 4277 } 4278 4279 bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options, 4280 bool highlight, bool last_child) { 4281 ValueObject *valobj = row.value.GetSP().get(); 4282 4283 if (valobj == nullptr) 4284 return false; 4285 4286 const char *type_name = 4287 options.show_types ? valobj->GetTypeName().GetCString() : nullptr; 4288 const char *name = valobj->GetName().GetCString(); 4289 const char *value = valobj->GetValueAsCString(); 4290 const char *summary = valobj->GetSummaryAsCString(); 4291 4292 window.MoveCursor(row.x, row.y); 4293 4294 row.DrawTree(window); 4295 4296 if (highlight) 4297 window.AttributeOn(A_REVERSE); 4298 4299 if (type_name && type_name[0]) 4300 window.PrintfTruncated(1, "(%s) ", type_name); 4301 4302 if (name && name[0]) 4303 window.PutCStringTruncated(1, name); 4304 4305 attr_t changd_attr = 0; 4306 if (valobj->GetValueDidChange()) 4307 changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD; 4308 4309 if (value && value[0]) { 4310 window.PutCStringTruncated(1, " = "); 4311 if (changd_attr) 4312 window.AttributeOn(changd_attr); 4313 window.PutCStringTruncated(1, value); 4314 if (changd_attr) 4315 window.AttributeOff(changd_attr); 4316 } 4317 4318 if (summary && summary[0]) { 4319 window.PutCStringTruncated(1, " "); 4320 if (changd_attr) 4321 window.AttributeOn(changd_attr); 4322 window.PutCStringTruncated(1, summary); 4323 if (changd_attr) 4324 window.AttributeOff(changd_attr); 4325 } 4326 4327 if (highlight) 4328 window.AttributeOff(A_REVERSE); 4329 4330 return true; 4331 } 4332 4333 void DisplayRows(Window &window, std::vector<Row> &rows, 4334 DisplayOptions &options) { 4335 // > 0x25B7 4336 // \/ 0x25BD 4337 4338 bool window_is_active = window.IsActive(); 4339 for (auto &row : rows) { 4340 const bool last_child = row.parent && &rows[rows.size() - 1] == &row; 4341 // Save the row index in each Row structure 4342 row.row_idx = m_num_rows; 4343 if ((m_num_rows >= m_first_visible_row) && 4344 ((m_num_rows - m_first_visible_row) < 4345 static_cast<size_t>(NumVisibleRows()))) { 4346 row.x = m_min_x; 4347 row.y = m_num_rows - m_first_visible_row + 1; 4348 if (DisplayRowObject(window, row, options, 4349 window_is_active && 4350 m_num_rows == m_selected_row_idx, 4351 last_child)) { 4352 ++m_num_rows; 4353 } else { 4354 row.x = 0; 4355 row.y = 0; 4356 } 4357 } else { 4358 row.x = 0; 4359 row.y = 0; 4360 ++m_num_rows; 4361 } 4362 4363 auto &children = row.GetChildren(); 4364 if (row.expanded && !children.empty()) { 4365 DisplayRows(window, children, options); 4366 } 4367 } 4368 } 4369 4370 int CalculateTotalNumberRows(std::vector<Row> &rows) { 4371 int row_count = 0; 4372 for (auto &row : rows) { 4373 ++row_count; 4374 if (row.expanded) 4375 row_count += CalculateTotalNumberRows(row.GetChildren()); 4376 } 4377 return row_count; 4378 } 4379 4380 static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) { 4381 for (auto &row : rows) { 4382 if (row_index == 0) 4383 return &row; 4384 else { 4385 --row_index; 4386 auto &children = row.GetChildren(); 4387 if (row.expanded && !children.empty()) { 4388 Row *result = GetRowForRowIndexImpl(children, row_index); 4389 if (result) 4390 return result; 4391 } 4392 } 4393 } 4394 return nullptr; 4395 } 4396 4397 Row *GetRowForRowIndex(size_t row_index) { 4398 return GetRowForRowIndexImpl(m_rows, row_index); 4399 } 4400 4401 int NumVisibleRows() const { return m_max_y - m_min_y; } 4402 4403 static DisplayOptions g_options; 4404 }; 4405 4406 class FrameVariablesWindowDelegate : public ValueObjectListDelegate { 4407 public: 4408 FrameVariablesWindowDelegate(Debugger &debugger) 4409 : ValueObjectListDelegate(), m_debugger(debugger), 4410 m_frame_block(nullptr) {} 4411 4412 ~FrameVariablesWindowDelegate() override = default; 4413 4414 const char *WindowDelegateGetHelpText() override { 4415 return "Frame variable window keyboard shortcuts:"; 4416 } 4417 4418 bool WindowDelegateDraw(Window &window, bool force) override { 4419 ExecutionContext exe_ctx( 4420 m_debugger.GetCommandInterpreter().GetExecutionContext()); 4421 Process *process = exe_ctx.GetProcessPtr(); 4422 Block *frame_block = nullptr; 4423 StackFrame *frame = nullptr; 4424 4425 if (process) { 4426 StateType state = process->GetState(); 4427 if (StateIsStoppedState(state, true)) { 4428 frame = exe_ctx.GetFramePtr(); 4429 if (frame) 4430 frame_block = frame->GetFrameBlock(); 4431 } else if (StateIsRunningState(state)) { 4432 return true; // Don't do any updating when we are running 4433 } 4434 } 4435 4436 ValueObjectList local_values; 4437 if (frame_block) { 4438 // Only update the variables if they have changed 4439 if (m_frame_block != frame_block) { 4440 m_frame_block = frame_block; 4441 4442 VariableList *locals = frame->GetVariableList(true); 4443 if (locals) { 4444 const DynamicValueType use_dynamic = eDynamicDontRunTarget; 4445 for (const VariableSP &local_sp : *locals) { 4446 ValueObjectSP value_sp = 4447 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic); 4448 if (value_sp) { 4449 ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue(); 4450 if (synthetic_value_sp) 4451 local_values.Append(synthetic_value_sp); 4452 else 4453 local_values.Append(value_sp); 4454 } 4455 } 4456 // Update the values 4457 SetValues(local_values); 4458 } 4459 } 4460 } else { 4461 m_frame_block = nullptr; 4462 // Update the values with an empty list if there is no frame 4463 SetValues(local_values); 4464 } 4465 4466 return ValueObjectListDelegate::WindowDelegateDraw(window, force); 4467 } 4468 4469 protected: 4470 Debugger &m_debugger; 4471 Block *m_frame_block; 4472 }; 4473 4474 class RegistersWindowDelegate : public ValueObjectListDelegate { 4475 public: 4476 RegistersWindowDelegate(Debugger &debugger) 4477 : ValueObjectListDelegate(), m_debugger(debugger) {} 4478 4479 ~RegistersWindowDelegate() override = default; 4480 4481 const char *WindowDelegateGetHelpText() override { 4482 return "Register window keyboard shortcuts:"; 4483 } 4484 4485 bool WindowDelegateDraw(Window &window, bool force) override { 4486 ExecutionContext exe_ctx( 4487 m_debugger.GetCommandInterpreter().GetExecutionContext()); 4488 StackFrame *frame = exe_ctx.GetFramePtr(); 4489 4490 ValueObjectList value_list; 4491 if (frame) { 4492 if (frame->GetStackID() != m_stack_id) { 4493 m_stack_id = frame->GetStackID(); 4494 RegisterContextSP reg_ctx(frame->GetRegisterContext()); 4495 if (reg_ctx) { 4496 const uint32_t num_sets = reg_ctx->GetRegisterSetCount(); 4497 for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) { 4498 value_list.Append( 4499 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx)); 4500 } 4501 } 4502 SetValues(value_list); 4503 } 4504 } else { 4505 Process *process = exe_ctx.GetProcessPtr(); 4506 if (process && process->IsAlive()) 4507 return true; // Don't do any updating if we are running 4508 else { 4509 // Update the values with an empty list if there is no process or the 4510 // process isn't alive anymore 4511 SetValues(value_list); 4512 } 4513 } 4514 return ValueObjectListDelegate::WindowDelegateDraw(window, force); 4515 } 4516 4517 protected: 4518 Debugger &m_debugger; 4519 StackID m_stack_id; 4520 }; 4521 4522 static const char *CursesKeyToCString(int ch) { 4523 static char g_desc[32]; 4524 if (ch >= KEY_F0 && ch < KEY_F0 + 64) { 4525 snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0); 4526 return g_desc; 4527 } 4528 switch (ch) { 4529 case KEY_DOWN: 4530 return "down"; 4531 case KEY_UP: 4532 return "up"; 4533 case KEY_LEFT: 4534 return "left"; 4535 case KEY_RIGHT: 4536 return "right"; 4537 case KEY_HOME: 4538 return "home"; 4539 case KEY_BACKSPACE: 4540 return "backspace"; 4541 case KEY_DL: 4542 return "delete-line"; 4543 case KEY_IL: 4544 return "insert-line"; 4545 case KEY_DC: 4546 return "delete-char"; 4547 case KEY_IC: 4548 return "insert-char"; 4549 case KEY_CLEAR: 4550 return "clear"; 4551 case KEY_EOS: 4552 return "clear-to-eos"; 4553 case KEY_EOL: 4554 return "clear-to-eol"; 4555 case KEY_SF: 4556 return "scroll-forward"; 4557 case KEY_SR: 4558 return "scroll-backward"; 4559 case KEY_NPAGE: 4560 return "page-down"; 4561 case KEY_PPAGE: 4562 return "page-up"; 4563 case KEY_STAB: 4564 return "set-tab"; 4565 case KEY_CTAB: 4566 return "clear-tab"; 4567 case KEY_CATAB: 4568 return "clear-all-tabs"; 4569 case KEY_ENTER: 4570 return "enter"; 4571 case KEY_PRINT: 4572 return "print"; 4573 case KEY_LL: 4574 return "lower-left key"; 4575 case KEY_A1: 4576 return "upper left of keypad"; 4577 case KEY_A3: 4578 return "upper right of keypad"; 4579 case KEY_B2: 4580 return "center of keypad"; 4581 case KEY_C1: 4582 return "lower left of keypad"; 4583 case KEY_C3: 4584 return "lower right of keypad"; 4585 case KEY_BTAB: 4586 return "back-tab key"; 4587 case KEY_BEG: 4588 return "begin key"; 4589 case KEY_CANCEL: 4590 return "cancel key"; 4591 case KEY_CLOSE: 4592 return "close key"; 4593 case KEY_COMMAND: 4594 return "command key"; 4595 case KEY_COPY: 4596 return "copy key"; 4597 case KEY_CREATE: 4598 return "create key"; 4599 case KEY_END: 4600 return "end key"; 4601 case KEY_EXIT: 4602 return "exit key"; 4603 case KEY_FIND: 4604 return "find key"; 4605 case KEY_HELP: 4606 return "help key"; 4607 case KEY_MARK: 4608 return "mark key"; 4609 case KEY_MESSAGE: 4610 return "message key"; 4611 case KEY_MOVE: 4612 return "move key"; 4613 case KEY_NEXT: 4614 return "next key"; 4615 case KEY_OPEN: 4616 return "open key"; 4617 case KEY_OPTIONS: 4618 return "options key"; 4619 case KEY_PREVIOUS: 4620 return "previous key"; 4621 case KEY_REDO: 4622 return "redo key"; 4623 case KEY_REFERENCE: 4624 return "reference key"; 4625 case KEY_REFRESH: 4626 return "refresh key"; 4627 case KEY_REPLACE: 4628 return "replace key"; 4629 case KEY_RESTART: 4630 return "restart key"; 4631 case KEY_RESUME: 4632 return "resume key"; 4633 case KEY_SAVE: 4634 return "save key"; 4635 case KEY_SBEG: 4636 return "shifted begin key"; 4637 case KEY_SCANCEL: 4638 return "shifted cancel key"; 4639 case KEY_SCOMMAND: 4640 return "shifted command key"; 4641 case KEY_SCOPY: 4642 return "shifted copy key"; 4643 case KEY_SCREATE: 4644 return "shifted create key"; 4645 case KEY_SDC: 4646 return "shifted delete-character key"; 4647 case KEY_SDL: 4648 return "shifted delete-line key"; 4649 case KEY_SELECT: 4650 return "select key"; 4651 case KEY_SEND: 4652 return "shifted end key"; 4653 case KEY_SEOL: 4654 return "shifted clear-to-end-of-line key"; 4655 case KEY_SEXIT: 4656 return "shifted exit key"; 4657 case KEY_SFIND: 4658 return "shifted find key"; 4659 case KEY_SHELP: 4660 return "shifted help key"; 4661 case KEY_SHOME: 4662 return "shifted home key"; 4663 case KEY_SIC: 4664 return "shifted insert-character key"; 4665 case KEY_SLEFT: 4666 return "shifted left-arrow key"; 4667 case KEY_SMESSAGE: 4668 return "shifted message key"; 4669 case KEY_SMOVE: 4670 return "shifted move key"; 4671 case KEY_SNEXT: 4672 return "shifted next key"; 4673 case KEY_SOPTIONS: 4674 return "shifted options key"; 4675 case KEY_SPREVIOUS: 4676 return "shifted previous key"; 4677 case KEY_SPRINT: 4678 return "shifted print key"; 4679 case KEY_SREDO: 4680 return "shifted redo key"; 4681 case KEY_SREPLACE: 4682 return "shifted replace key"; 4683 case KEY_SRIGHT: 4684 return "shifted right-arrow key"; 4685 case KEY_SRSUME: 4686 return "shifted resume key"; 4687 case KEY_SSAVE: 4688 return "shifted save key"; 4689 case KEY_SSUSPEND: 4690 return "shifted suspend key"; 4691 case KEY_SUNDO: 4692 return "shifted undo key"; 4693 case KEY_SUSPEND: 4694 return "suspend key"; 4695 case KEY_UNDO: 4696 return "undo key"; 4697 case KEY_MOUSE: 4698 return "Mouse event has occurred"; 4699 case KEY_RESIZE: 4700 return "Terminal resize event"; 4701 #ifdef KEY_EVENT 4702 case KEY_EVENT: 4703 return "We were interrupted by an event"; 4704 #endif 4705 case KEY_RETURN: 4706 return "return"; 4707 case ' ': 4708 return "space"; 4709 case '\t': 4710 return "tab"; 4711 case KEY_ESCAPE: 4712 return "escape"; 4713 default: 4714 if (llvm::isPrint(ch)) 4715 snprintf(g_desc, sizeof(g_desc), "%c", ch); 4716 else 4717 snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch); 4718 return g_desc; 4719 } 4720 return nullptr; 4721 } 4722 4723 HelpDialogDelegate::HelpDialogDelegate(const char *text, 4724 KeyHelp *key_help_array) 4725 : m_text(), m_first_visible_line(0) { 4726 if (text && text[0]) { 4727 m_text.SplitIntoLines(text); 4728 m_text.AppendString(""); 4729 } 4730 if (key_help_array) { 4731 for (KeyHelp *key = key_help_array; key->ch; ++key) { 4732 StreamString key_description; 4733 key_description.Printf("%10s - %s", CursesKeyToCString(key->ch), 4734 key->description); 4735 m_text.AppendString(key_description.GetString()); 4736 } 4737 } 4738 } 4739 4740 HelpDialogDelegate::~HelpDialogDelegate() = default; 4741 4742 bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) { 4743 window.Erase(); 4744 const int window_height = window.GetHeight(); 4745 int x = 2; 4746 int y = 1; 4747 const int min_y = y; 4748 const int max_y = window_height - 1 - y; 4749 const size_t num_visible_lines = max_y - min_y + 1; 4750 const size_t num_lines = m_text.GetSize(); 4751 const char *bottom_message; 4752 if (num_lines <= num_visible_lines) 4753 bottom_message = "Press any key to exit"; 4754 else 4755 bottom_message = "Use arrows to scroll, any other key to exit"; 4756 window.DrawTitleBox(window.GetName(), bottom_message); 4757 while (y <= max_y) { 4758 window.MoveCursor(x, y); 4759 window.PutCStringTruncated( 4760 1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y)); 4761 ++y; 4762 } 4763 return true; 4764 } 4765 4766 HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window, 4767 int key) { 4768 bool done = false; 4769 const size_t num_lines = m_text.GetSize(); 4770 const size_t num_visible_lines = window.GetHeight() - 2; 4771 4772 if (num_lines <= num_visible_lines) { 4773 done = true; 4774 // If we have all lines visible and don't need scrolling, then any key 4775 // press will cause us to exit 4776 } else { 4777 switch (key) { 4778 case KEY_UP: 4779 if (m_first_visible_line > 0) 4780 --m_first_visible_line; 4781 break; 4782 4783 case KEY_DOWN: 4784 if (m_first_visible_line + num_visible_lines < num_lines) 4785 ++m_first_visible_line; 4786 break; 4787 4788 case KEY_PPAGE: 4789 case ',': 4790 if (m_first_visible_line > 0) { 4791 if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines) 4792 m_first_visible_line -= num_visible_lines; 4793 else 4794 m_first_visible_line = 0; 4795 } 4796 break; 4797 4798 case KEY_NPAGE: 4799 case '.': 4800 if (m_first_visible_line + num_visible_lines < num_lines) { 4801 m_first_visible_line += num_visible_lines; 4802 if (static_cast<size_t>(m_first_visible_line) > num_lines) 4803 m_first_visible_line = num_lines - num_visible_lines; 4804 } 4805 break; 4806 4807 default: 4808 done = true; 4809 break; 4810 } 4811 } 4812 if (done) 4813 window.GetParent()->RemoveSubWindow(&window); 4814 return eKeyHandled; 4815 } 4816 4817 class ApplicationDelegate : public WindowDelegate, public MenuDelegate { 4818 public: 4819 enum { 4820 eMenuID_LLDB = 1, 4821 eMenuID_LLDBAbout, 4822 eMenuID_LLDBExit, 4823 4824 eMenuID_Target, 4825 eMenuID_TargetCreate, 4826 eMenuID_TargetDelete, 4827 4828 eMenuID_Process, 4829 eMenuID_ProcessAttach, 4830 eMenuID_ProcessDetachResume, 4831 eMenuID_ProcessDetachSuspended, 4832 eMenuID_ProcessLaunch, 4833 eMenuID_ProcessContinue, 4834 eMenuID_ProcessHalt, 4835 eMenuID_ProcessKill, 4836 4837 eMenuID_Thread, 4838 eMenuID_ThreadStepIn, 4839 eMenuID_ThreadStepOver, 4840 eMenuID_ThreadStepOut, 4841 4842 eMenuID_View, 4843 eMenuID_ViewBacktrace, 4844 eMenuID_ViewRegisters, 4845 eMenuID_ViewSource, 4846 eMenuID_ViewVariables, 4847 4848 eMenuID_Help, 4849 eMenuID_HelpGUIHelp 4850 }; 4851 4852 ApplicationDelegate(Application &app, Debugger &debugger) 4853 : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {} 4854 4855 ~ApplicationDelegate() override = default; 4856 4857 bool WindowDelegateDraw(Window &window, bool force) override { 4858 return false; // Drawing not handled, let standard window drawing happen 4859 } 4860 4861 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { 4862 switch (key) { 4863 case '\t': 4864 window.SelectNextWindowAsActive(); 4865 return eKeyHandled; 4866 4867 case KEY_SHIFT_TAB: 4868 window.SelectPreviousWindowAsActive(); 4869 return eKeyHandled; 4870 4871 case 'h': 4872 window.CreateHelpSubwindow(); 4873 return eKeyHandled; 4874 4875 case KEY_ESCAPE: 4876 return eQuitApplication; 4877 4878 default: 4879 break; 4880 } 4881 return eKeyNotHandled; 4882 } 4883 4884 const char *WindowDelegateGetHelpText() override { 4885 return "Welcome to the LLDB curses GUI.\n\n" 4886 "Press the TAB key to change the selected view.\n" 4887 "Each view has its own keyboard shortcuts, press 'h' to open a " 4888 "dialog to display them.\n\n" 4889 "Common key bindings for all views:"; 4890 } 4891 4892 KeyHelp *WindowDelegateGetKeyHelp() override { 4893 static curses::KeyHelp g_source_view_key_help[] = { 4894 {'\t', "Select next view"}, 4895 {KEY_BTAB, "Select previous view"}, 4896 {'h', "Show help dialog with view specific key bindings"}, 4897 {',', "Page up"}, 4898 {'.', "Page down"}, 4899 {KEY_UP, "Select previous"}, 4900 {KEY_DOWN, "Select next"}, 4901 {KEY_LEFT, "Unexpand or select parent"}, 4902 {KEY_RIGHT, "Expand"}, 4903 {KEY_PPAGE, "Page up"}, 4904 {KEY_NPAGE, "Page down"}, 4905 {'\0', nullptr}}; 4906 return g_source_view_key_help; 4907 } 4908 4909 MenuActionResult MenuDelegateAction(Menu &menu) override { 4910 switch (menu.GetIdentifier()) { 4911 case eMenuID_ThreadStepIn: { 4912 ExecutionContext exe_ctx = 4913 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4914 if (exe_ctx.HasThreadScope()) { 4915 Process *process = exe_ctx.GetProcessPtr(); 4916 if (process && process->IsAlive() && 4917 StateIsStoppedState(process->GetState(), true)) 4918 exe_ctx.GetThreadRef().StepIn(true); 4919 } 4920 } 4921 return MenuActionResult::Handled; 4922 4923 case eMenuID_ThreadStepOut: { 4924 ExecutionContext exe_ctx = 4925 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4926 if (exe_ctx.HasThreadScope()) { 4927 Process *process = exe_ctx.GetProcessPtr(); 4928 if (process && process->IsAlive() && 4929 StateIsStoppedState(process->GetState(), true)) 4930 exe_ctx.GetThreadRef().StepOut(); 4931 } 4932 } 4933 return MenuActionResult::Handled; 4934 4935 case eMenuID_ThreadStepOver: { 4936 ExecutionContext exe_ctx = 4937 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4938 if (exe_ctx.HasThreadScope()) { 4939 Process *process = exe_ctx.GetProcessPtr(); 4940 if (process && process->IsAlive() && 4941 StateIsStoppedState(process->GetState(), true)) 4942 exe_ctx.GetThreadRef().StepOver(true); 4943 } 4944 } 4945 return MenuActionResult::Handled; 4946 4947 case eMenuID_ProcessAttach: { 4948 WindowSP main_window_sp = m_app.GetMainWindow(); 4949 FormDelegateSP form_delegate_sp = FormDelegateSP( 4950 new ProcessAttachFormDelegate(m_debugger, main_window_sp)); 4951 Rect bounds = main_window_sp->GetCenteredRect(80, 22); 4952 WindowSP form_window_sp = main_window_sp->CreateSubWindow( 4953 form_delegate_sp->GetName().c_str(), bounds, true); 4954 WindowDelegateSP window_delegate_sp = 4955 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); 4956 form_window_sp->SetDelegate(window_delegate_sp); 4957 return MenuActionResult::Handled; 4958 } 4959 4960 case eMenuID_ProcessContinue: { 4961 ExecutionContext exe_ctx = 4962 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4963 if (exe_ctx.HasProcessScope()) { 4964 Process *process = exe_ctx.GetProcessPtr(); 4965 if (process && process->IsAlive() && 4966 StateIsStoppedState(process->GetState(), true)) 4967 process->Resume(); 4968 } 4969 } 4970 return MenuActionResult::Handled; 4971 4972 case eMenuID_ProcessKill: { 4973 ExecutionContext exe_ctx = 4974 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4975 if (exe_ctx.HasProcessScope()) { 4976 Process *process = exe_ctx.GetProcessPtr(); 4977 if (process && process->IsAlive()) 4978 process->Destroy(false); 4979 } 4980 } 4981 return MenuActionResult::Handled; 4982 4983 case eMenuID_ProcessHalt: { 4984 ExecutionContext exe_ctx = 4985 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4986 if (exe_ctx.HasProcessScope()) { 4987 Process *process = exe_ctx.GetProcessPtr(); 4988 if (process && process->IsAlive()) 4989 process->Halt(); 4990 } 4991 } 4992 return MenuActionResult::Handled; 4993 4994 case eMenuID_ProcessDetachResume: 4995 case eMenuID_ProcessDetachSuspended: { 4996 ExecutionContext exe_ctx = 4997 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4998 if (exe_ctx.HasProcessScope()) { 4999 Process *process = exe_ctx.GetProcessPtr(); 5000 if (process && process->IsAlive()) 5001 process->Detach(menu.GetIdentifier() == 5002 eMenuID_ProcessDetachSuspended); 5003 } 5004 } 5005 return MenuActionResult::Handled; 5006 5007 case eMenuID_Process: { 5008 // Populate the menu with all of the threads if the process is stopped 5009 // when the Process menu gets selected and is about to display its 5010 // submenu. 5011 Menus &submenus = menu.GetSubmenus(); 5012 ExecutionContext exe_ctx = 5013 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5014 Process *process = exe_ctx.GetProcessPtr(); 5015 if (process && process->IsAlive() && 5016 StateIsStoppedState(process->GetState(), true)) { 5017 if (submenus.size() == 7) 5018 menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 5019 else if (submenus.size() > 8) 5020 submenus.erase(submenus.begin() + 8, submenus.end()); 5021 5022 ThreadList &threads = process->GetThreadList(); 5023 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 5024 size_t num_threads = threads.GetSize(); 5025 for (size_t i = 0; i < num_threads; ++i) { 5026 ThreadSP thread_sp = threads.GetThreadAtIndex(i); 5027 char menu_char = '\0'; 5028 if (i < 9) 5029 menu_char = '1' + i; 5030 StreamString thread_menu_title; 5031 thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID()); 5032 const char *thread_name = thread_sp->GetName(); 5033 if (thread_name && thread_name[0]) 5034 thread_menu_title.Printf(" %s", thread_name); 5035 else { 5036 const char *queue_name = thread_sp->GetQueueName(); 5037 if (queue_name && queue_name[0]) 5038 thread_menu_title.Printf(" %s", queue_name); 5039 } 5040 menu.AddSubmenu( 5041 MenuSP(new Menu(thread_menu_title.GetString().str().c_str(), 5042 nullptr, menu_char, thread_sp->GetID()))); 5043 } 5044 } else if (submenus.size() > 7) { 5045 // Remove the separator and any other thread submenu items that were 5046 // previously added 5047 submenus.erase(submenus.begin() + 7, submenus.end()); 5048 } 5049 // Since we are adding and removing items we need to recalculate the name 5050 // lengths 5051 menu.RecalculateNameLengths(); 5052 } 5053 return MenuActionResult::Handled; 5054 5055 case eMenuID_ViewVariables: { 5056 WindowSP main_window_sp = m_app.GetMainWindow(); 5057 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 5058 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 5059 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 5060 const Rect source_bounds = source_window_sp->GetBounds(); 5061 5062 if (variables_window_sp) { 5063 const Rect variables_bounds = variables_window_sp->GetBounds(); 5064 5065 main_window_sp->RemoveSubWindow(variables_window_sp.get()); 5066 5067 if (registers_window_sp) { 5068 // We have a registers window, so give all the area back to the 5069 // registers window 5070 Rect registers_bounds = variables_bounds; 5071 registers_bounds.size.width = source_bounds.size.width; 5072 registers_window_sp->SetBounds(registers_bounds); 5073 } else { 5074 // We have no registers window showing so give the bottom area back 5075 // to the source view 5076 source_window_sp->Resize(source_bounds.size.width, 5077 source_bounds.size.height + 5078 variables_bounds.size.height); 5079 } 5080 } else { 5081 Rect new_variables_rect; 5082 if (registers_window_sp) { 5083 // We have a registers window so split the area of the registers 5084 // window into two columns where the left hand side will be the 5085 // variables and the right hand side will be the registers 5086 const Rect variables_bounds = registers_window_sp->GetBounds(); 5087 Rect new_registers_rect; 5088 variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect, 5089 new_registers_rect); 5090 registers_window_sp->SetBounds(new_registers_rect); 5091 } else { 5092 // No registers window, grab the bottom part of the source window 5093 Rect new_source_rect; 5094 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, 5095 new_variables_rect); 5096 source_window_sp->SetBounds(new_source_rect); 5097 } 5098 WindowSP new_window_sp = main_window_sp->CreateSubWindow( 5099 "Variables", new_variables_rect, false); 5100 new_window_sp->SetDelegate( 5101 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 5102 } 5103 touchwin(stdscr); 5104 } 5105 return MenuActionResult::Handled; 5106 5107 case eMenuID_ViewRegisters: { 5108 WindowSP main_window_sp = m_app.GetMainWindow(); 5109 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 5110 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 5111 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 5112 const Rect source_bounds = source_window_sp->GetBounds(); 5113 5114 if (registers_window_sp) { 5115 if (variables_window_sp) { 5116 const Rect variables_bounds = variables_window_sp->GetBounds(); 5117 5118 // We have a variables window, so give all the area back to the 5119 // variables window 5120 variables_window_sp->Resize(variables_bounds.size.width + 5121 registers_window_sp->GetWidth(), 5122 variables_bounds.size.height); 5123 } else { 5124 // We have no variables window showing so give the bottom area back 5125 // to the source view 5126 source_window_sp->Resize(source_bounds.size.width, 5127 source_bounds.size.height + 5128 registers_window_sp->GetHeight()); 5129 } 5130 main_window_sp->RemoveSubWindow(registers_window_sp.get()); 5131 } else { 5132 Rect new_regs_rect; 5133 if (variables_window_sp) { 5134 // We have a variables window, split it into two columns where the 5135 // left hand side will be the variables and the right hand side will 5136 // be the registers 5137 const Rect variables_bounds = variables_window_sp->GetBounds(); 5138 Rect new_vars_rect; 5139 variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect, 5140 new_regs_rect); 5141 variables_window_sp->SetBounds(new_vars_rect); 5142 } else { 5143 // No variables window, grab the bottom part of the source window 5144 Rect new_source_rect; 5145 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, 5146 new_regs_rect); 5147 source_window_sp->SetBounds(new_source_rect); 5148 } 5149 WindowSP new_window_sp = 5150 main_window_sp->CreateSubWindow("Registers", new_regs_rect, false); 5151 new_window_sp->SetDelegate( 5152 WindowDelegateSP(new RegistersWindowDelegate(m_debugger))); 5153 } 5154 touchwin(stdscr); 5155 } 5156 return MenuActionResult::Handled; 5157 5158 case eMenuID_HelpGUIHelp: 5159 m_app.GetMainWindow()->CreateHelpSubwindow(); 5160 return MenuActionResult::Handled; 5161 5162 default: 5163 break; 5164 } 5165 5166 return MenuActionResult::NotHandled; 5167 } 5168 5169 protected: 5170 Application &m_app; 5171 Debugger &m_debugger; 5172 }; 5173 5174 class StatusBarWindowDelegate : public WindowDelegate { 5175 public: 5176 StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) { 5177 FormatEntity::Parse("Thread: ${thread.id%tid}", m_format); 5178 } 5179 5180 ~StatusBarWindowDelegate() override = default; 5181 5182 bool WindowDelegateDraw(Window &window, bool force) override { 5183 ExecutionContext exe_ctx = 5184 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5185 Process *process = exe_ctx.GetProcessPtr(); 5186 Thread *thread = exe_ctx.GetThreadPtr(); 5187 StackFrame *frame = exe_ctx.GetFramePtr(); 5188 window.Erase(); 5189 window.SetBackground(BlackOnWhite); 5190 window.MoveCursor(0, 0); 5191 if (process) { 5192 const StateType state = process->GetState(); 5193 window.Printf("Process: %5" PRIu64 " %10s", process->GetID(), 5194 StateAsCString(state)); 5195 5196 if (StateIsStoppedState(state, true)) { 5197 StreamString strm; 5198 if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, 5199 nullptr, nullptr, false, false)) { 5200 window.MoveCursor(40, 0); 5201 window.PutCStringTruncated(1, strm.GetString().str().c_str()); 5202 } 5203 5204 window.MoveCursor(60, 0); 5205 if (frame) 5206 window.Printf("Frame: %3u PC = 0x%16.16" PRIx64, 5207 frame->GetFrameIndex(), 5208 frame->GetFrameCodeAddress().GetOpcodeLoadAddress( 5209 exe_ctx.GetTargetPtr())); 5210 } else if (state == eStateExited) { 5211 const char *exit_desc = process->GetExitDescription(); 5212 const int exit_status = process->GetExitStatus(); 5213 if (exit_desc && exit_desc[0]) 5214 window.Printf(" with status = %i (%s)", exit_status, exit_desc); 5215 else 5216 window.Printf(" with status = %i", exit_status); 5217 } 5218 } 5219 return true; 5220 } 5221 5222 protected: 5223 Debugger &m_debugger; 5224 FormatEntity::Entry m_format; 5225 }; 5226 5227 class SourceFileWindowDelegate : public WindowDelegate { 5228 public: 5229 SourceFileWindowDelegate(Debugger &debugger) 5230 : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(), 5231 m_disassembly_scope(nullptr), m_disassembly_sp(), m_disassembly_range(), 5232 m_title(), m_tid(LLDB_INVALID_THREAD_ID), m_line_width(4), 5233 m_selected_line(0), m_pc_line(0), m_stop_id(0), m_frame_idx(UINT32_MAX), 5234 m_first_visible_line(0), m_first_visible_column(0), m_min_x(0), 5235 m_min_y(0), m_max_x(0), m_max_y(0) {} 5236 5237 ~SourceFileWindowDelegate() override = default; 5238 5239 void Update(const SymbolContext &sc) { m_sc = sc; } 5240 5241 uint32_t NumVisibleLines() const { return m_max_y - m_min_y; } 5242 5243 const char *WindowDelegateGetHelpText() override { 5244 return "Source/Disassembly window keyboard shortcuts:"; 5245 } 5246 5247 KeyHelp *WindowDelegateGetKeyHelp() override { 5248 static curses::KeyHelp g_source_view_key_help[] = { 5249 {KEY_RETURN, "Run to selected line with one shot breakpoint"}, 5250 {KEY_UP, "Select previous source line"}, 5251 {KEY_DOWN, "Select next source line"}, 5252 {KEY_LEFT, "Scroll to the left"}, 5253 {KEY_RIGHT, "Scroll to the right"}, 5254 {KEY_PPAGE, "Page up"}, 5255 {KEY_NPAGE, "Page down"}, 5256 {'b', "Set breakpoint on selected source/disassembly line"}, 5257 {'c', "Continue process"}, 5258 {'D', "Detach with process suspended"}, 5259 {'h', "Show help dialog"}, 5260 {'n', "Step over (source line)"}, 5261 {'N', "Step over (single instruction)"}, 5262 {'f', "Step out (finish)"}, 5263 {'s', "Step in (source line)"}, 5264 {'S', "Step in (single instruction)"}, 5265 {'u', "Frame up"}, 5266 {'d', "Frame down"}, 5267 {',', "Page up"}, 5268 {'.', "Page down"}, 5269 {'\0', nullptr}}; 5270 return g_source_view_key_help; 5271 } 5272 5273 bool WindowDelegateDraw(Window &window, bool force) override { 5274 ExecutionContext exe_ctx = 5275 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5276 Process *process = exe_ctx.GetProcessPtr(); 5277 Thread *thread = nullptr; 5278 5279 bool update_location = false; 5280 if (process) { 5281 StateType state = process->GetState(); 5282 if (StateIsStoppedState(state, true)) { 5283 // We are stopped, so it is ok to 5284 update_location = true; 5285 } 5286 } 5287 5288 m_min_x = 1; 5289 m_min_y = 2; 5290 m_max_x = window.GetMaxX() - 1; 5291 m_max_y = window.GetMaxY() - 1; 5292 5293 const uint32_t num_visible_lines = NumVisibleLines(); 5294 StackFrameSP frame_sp; 5295 bool set_selected_line_to_pc = false; 5296 5297 if (update_location) { 5298 const bool process_alive = process ? process->IsAlive() : false; 5299 bool thread_changed = false; 5300 if (process_alive) { 5301 thread = exe_ctx.GetThreadPtr(); 5302 if (thread) { 5303 frame_sp = thread->GetSelectedFrame(); 5304 auto tid = thread->GetID(); 5305 thread_changed = tid != m_tid; 5306 m_tid = tid; 5307 } else { 5308 if (m_tid != LLDB_INVALID_THREAD_ID) { 5309 thread_changed = true; 5310 m_tid = LLDB_INVALID_THREAD_ID; 5311 } 5312 } 5313 } 5314 const uint32_t stop_id = process ? process->GetStopID() : 0; 5315 const bool stop_id_changed = stop_id != m_stop_id; 5316 bool frame_changed = false; 5317 m_stop_id = stop_id; 5318 m_title.Clear(); 5319 if (frame_sp) { 5320 m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything); 5321 if (m_sc.module_sp) { 5322 m_title.Printf( 5323 "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString()); 5324 ConstString func_name = m_sc.GetFunctionName(); 5325 if (func_name) 5326 m_title.Printf("`%s", func_name.GetCString()); 5327 } 5328 const uint32_t frame_idx = frame_sp->GetFrameIndex(); 5329 frame_changed = frame_idx != m_frame_idx; 5330 m_frame_idx = frame_idx; 5331 } else { 5332 m_sc.Clear(true); 5333 frame_changed = m_frame_idx != UINT32_MAX; 5334 m_frame_idx = UINT32_MAX; 5335 } 5336 5337 const bool context_changed = 5338 thread_changed || frame_changed || stop_id_changed; 5339 5340 if (process_alive) { 5341 if (m_sc.line_entry.IsValid()) { 5342 m_pc_line = m_sc.line_entry.line; 5343 if (m_pc_line != UINT32_MAX) 5344 --m_pc_line; // Convert to zero based line number... 5345 // Update the selected line if the stop ID changed... 5346 if (context_changed) 5347 m_selected_line = m_pc_line; 5348 5349 if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) { 5350 // Same file, nothing to do, we should either have the lines or not 5351 // (source file missing) 5352 if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) { 5353 if (m_selected_line >= m_first_visible_line + num_visible_lines) 5354 m_first_visible_line = m_selected_line - 10; 5355 } else { 5356 if (m_selected_line > 10) 5357 m_first_visible_line = m_selected_line - 10; 5358 else 5359 m_first_visible_line = 0; 5360 } 5361 } else { 5362 // File changed, set selected line to the line with the PC 5363 m_selected_line = m_pc_line; 5364 m_file_sp = 5365 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file); 5366 if (m_file_sp) { 5367 const size_t num_lines = m_file_sp->GetNumLines(); 5368 m_line_width = 1; 5369 for (size_t n = num_lines; n >= 10; n = n / 10) 5370 ++m_line_width; 5371 5372 if (num_lines < num_visible_lines || 5373 m_selected_line < num_visible_lines) 5374 m_first_visible_line = 0; 5375 else 5376 m_first_visible_line = m_selected_line - 10; 5377 } 5378 } 5379 } else { 5380 m_file_sp.reset(); 5381 } 5382 5383 if (!m_file_sp || m_file_sp->GetNumLines() == 0) { 5384 // Show disassembly 5385 bool prefer_file_cache = false; 5386 if (m_sc.function) { 5387 if (m_disassembly_scope != m_sc.function) { 5388 m_disassembly_scope = m_sc.function; 5389 m_disassembly_sp = m_sc.function->GetInstructions( 5390 exe_ctx, nullptr, !prefer_file_cache); 5391 if (m_disassembly_sp) { 5392 set_selected_line_to_pc = true; 5393 m_disassembly_range = m_sc.function->GetAddressRange(); 5394 } else { 5395 m_disassembly_range.Clear(); 5396 } 5397 } else { 5398 set_selected_line_to_pc = context_changed; 5399 } 5400 } else if (m_sc.symbol) { 5401 if (m_disassembly_scope != m_sc.symbol) { 5402 m_disassembly_scope = m_sc.symbol; 5403 m_disassembly_sp = m_sc.symbol->GetInstructions( 5404 exe_ctx, nullptr, prefer_file_cache); 5405 if (m_disassembly_sp) { 5406 set_selected_line_to_pc = true; 5407 m_disassembly_range.GetBaseAddress() = 5408 m_sc.symbol->GetAddress(); 5409 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize()); 5410 } else { 5411 m_disassembly_range.Clear(); 5412 } 5413 } else { 5414 set_selected_line_to_pc = context_changed; 5415 } 5416 } 5417 } 5418 } else { 5419 m_pc_line = UINT32_MAX; 5420 } 5421 } 5422 5423 const int window_width = window.GetWidth(); 5424 window.Erase(); 5425 window.DrawTitleBox("Sources"); 5426 if (!m_title.GetString().empty()) { 5427 window.AttributeOn(A_REVERSE); 5428 window.MoveCursor(1, 1); 5429 window.PutChar(' '); 5430 window.PutCStringTruncated(1, m_title.GetString().str().c_str()); 5431 int x = window.GetCursorX(); 5432 if (x < window_width - 1) { 5433 window.Printf("%*s", window_width - x - 1, ""); 5434 } 5435 window.AttributeOff(A_REVERSE); 5436 } 5437 5438 Target *target = exe_ctx.GetTargetPtr(); 5439 const size_t num_source_lines = GetNumSourceLines(); 5440 if (num_source_lines > 0) { 5441 // Display source 5442 BreakpointLines bp_lines; 5443 if (target) { 5444 BreakpointList &bp_list = target->GetBreakpointList(); 5445 const size_t num_bps = bp_list.GetSize(); 5446 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 5447 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 5448 const size_t num_bps_locs = bp_sp->GetNumLocations(); 5449 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 5450 BreakpointLocationSP bp_loc_sp = 5451 bp_sp->GetLocationAtIndex(bp_loc_idx); 5452 LineEntry bp_loc_line_entry; 5453 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( 5454 bp_loc_line_entry)) { 5455 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) { 5456 bp_lines.insert(bp_loc_line_entry.line); 5457 } 5458 } 5459 } 5460 } 5461 } 5462 5463 const attr_t selected_highlight_attr = A_REVERSE; 5464 const attr_t pc_highlight_attr = COLOR_PAIR(BlackOnBlue); 5465 5466 for (size_t i = 0; i < num_visible_lines; ++i) { 5467 const uint32_t curr_line = m_first_visible_line + i; 5468 if (curr_line < num_source_lines) { 5469 const int line_y = m_min_y + i; 5470 window.MoveCursor(1, line_y); 5471 const bool is_pc_line = curr_line == m_pc_line; 5472 const bool line_is_selected = m_selected_line == curr_line; 5473 // Highlight the line as the PC line first, then if the selected line 5474 // isn't the same as the PC line, highlight it differently 5475 attr_t highlight_attr = 0; 5476 attr_t bp_attr = 0; 5477 if (is_pc_line) 5478 highlight_attr = pc_highlight_attr; 5479 else if (line_is_selected) 5480 highlight_attr = selected_highlight_attr; 5481 5482 if (bp_lines.find(curr_line + 1) != bp_lines.end()) 5483 bp_attr = COLOR_PAIR(BlackOnWhite); 5484 5485 if (bp_attr) 5486 window.AttributeOn(bp_attr); 5487 5488 window.Printf(" %*u ", m_line_width, curr_line + 1); 5489 5490 if (bp_attr) 5491 window.AttributeOff(bp_attr); 5492 5493 window.PutChar(ACS_VLINE); 5494 // Mark the line with the PC with a diamond 5495 if (is_pc_line) 5496 window.PutChar(ACS_DIAMOND); 5497 else 5498 window.PutChar(' '); 5499 5500 if (highlight_attr) 5501 window.AttributeOn(highlight_attr); 5502 5503 StreamString lineStream; 5504 m_file_sp->DisplaySourceLines(curr_line + 1, {}, 0, 0, &lineStream); 5505 StringRef line = lineStream.GetString(); 5506 if (line.endswith("\n")) 5507 line = line.drop_back(); 5508 bool wasWritten = window.OutputColoredStringTruncated( 5509 1, line, m_first_visible_column, line_is_selected); 5510 if (line_is_selected && !wasWritten) { 5511 // Draw an empty space to show the selected line if empty, 5512 // or draw '<' if nothing is visible because of scrolling too much 5513 // to the right. 5514 window.PutCStringTruncated( 5515 1, line.empty() && m_first_visible_column == 0 ? " " : "<"); 5516 } 5517 5518 if (is_pc_line && frame_sp && 5519 frame_sp->GetConcreteFrameIndex() == 0) { 5520 StopInfoSP stop_info_sp; 5521 if (thread) 5522 stop_info_sp = thread->GetStopInfo(); 5523 if (stop_info_sp) { 5524 const char *stop_description = stop_info_sp->GetDescription(); 5525 if (stop_description && stop_description[0]) { 5526 size_t stop_description_len = strlen(stop_description); 5527 int desc_x = window_width - stop_description_len - 16; 5528 if (desc_x - window.GetCursorX() > 0) 5529 window.Printf("%*s", desc_x - window.GetCursorX(), ""); 5530 window.MoveCursor(window_width - stop_description_len - 16, 5531 line_y); 5532 const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue); 5533 window.AttributeOn(stop_reason_attr); 5534 window.PrintfTruncated(1, " <<< Thread %u: %s ", 5535 thread->GetIndexID(), stop_description); 5536 window.AttributeOff(stop_reason_attr); 5537 } 5538 } else { 5539 window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); 5540 } 5541 } 5542 if (highlight_attr) 5543 window.AttributeOff(highlight_attr); 5544 } else { 5545 break; 5546 } 5547 } 5548 } else { 5549 size_t num_disassembly_lines = GetNumDisassemblyLines(); 5550 if (num_disassembly_lines > 0) { 5551 // Display disassembly 5552 BreakpointAddrs bp_file_addrs; 5553 Target *target = exe_ctx.GetTargetPtr(); 5554 if (target) { 5555 BreakpointList &bp_list = target->GetBreakpointList(); 5556 const size_t num_bps = bp_list.GetSize(); 5557 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 5558 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 5559 const size_t num_bps_locs = bp_sp->GetNumLocations(); 5560 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; 5561 ++bp_loc_idx) { 5562 BreakpointLocationSP bp_loc_sp = 5563 bp_sp->GetLocationAtIndex(bp_loc_idx); 5564 LineEntry bp_loc_line_entry; 5565 const lldb::addr_t file_addr = 5566 bp_loc_sp->GetAddress().GetFileAddress(); 5567 if (file_addr != LLDB_INVALID_ADDRESS) { 5568 if (m_disassembly_range.ContainsFileAddress(file_addr)) 5569 bp_file_addrs.insert(file_addr); 5570 } 5571 } 5572 } 5573 } 5574 5575 const attr_t selected_highlight_attr = A_REVERSE; 5576 const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue); 5577 5578 StreamString strm; 5579 5580 InstructionList &insts = m_disassembly_sp->GetInstructionList(); 5581 Address pc_address; 5582 5583 if (frame_sp) 5584 pc_address = frame_sp->GetFrameCodeAddress(); 5585 const uint32_t pc_idx = 5586 pc_address.IsValid() 5587 ? insts.GetIndexOfInstructionAtAddress(pc_address) 5588 : UINT32_MAX; 5589 if (set_selected_line_to_pc) { 5590 m_selected_line = pc_idx; 5591 } 5592 5593 const uint32_t non_visible_pc_offset = (num_visible_lines / 5); 5594 if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines) 5595 m_first_visible_line = 0; 5596 5597 if (pc_idx < num_disassembly_lines) { 5598 if (pc_idx < static_cast<uint32_t>(m_first_visible_line) || 5599 pc_idx >= m_first_visible_line + num_visible_lines) 5600 m_first_visible_line = pc_idx - non_visible_pc_offset; 5601 } 5602 5603 for (size_t i = 0; i < num_visible_lines; ++i) { 5604 const uint32_t inst_idx = m_first_visible_line + i; 5605 Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get(); 5606 if (!inst) 5607 break; 5608 5609 const int line_y = m_min_y + i; 5610 window.MoveCursor(1, line_y); 5611 const bool is_pc_line = frame_sp && inst_idx == pc_idx; 5612 const bool line_is_selected = m_selected_line == inst_idx; 5613 // Highlight the line as the PC line first, then if the selected line 5614 // isn't the same as the PC line, highlight it differently 5615 attr_t highlight_attr = 0; 5616 attr_t bp_attr = 0; 5617 if (is_pc_line) 5618 highlight_attr = pc_highlight_attr; 5619 else if (line_is_selected) 5620 highlight_attr = selected_highlight_attr; 5621 5622 if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) != 5623 bp_file_addrs.end()) 5624 bp_attr = COLOR_PAIR(BlackOnWhite); 5625 5626 if (bp_attr) 5627 window.AttributeOn(bp_attr); 5628 5629 window.Printf(" 0x%16.16llx ", 5630 static_cast<unsigned long long>( 5631 inst->GetAddress().GetLoadAddress(target))); 5632 5633 if (bp_attr) 5634 window.AttributeOff(bp_attr); 5635 5636 window.PutChar(ACS_VLINE); 5637 // Mark the line with the PC with a diamond 5638 if (is_pc_line) 5639 window.PutChar(ACS_DIAMOND); 5640 else 5641 window.PutChar(' '); 5642 5643 if (highlight_attr) 5644 window.AttributeOn(highlight_attr); 5645 5646 const char *mnemonic = inst->GetMnemonic(&exe_ctx); 5647 const char *operands = inst->GetOperands(&exe_ctx); 5648 const char *comment = inst->GetComment(&exe_ctx); 5649 5650 if (mnemonic != nullptr && mnemonic[0] == '\0') 5651 mnemonic = nullptr; 5652 if (operands != nullptr && operands[0] == '\0') 5653 operands = nullptr; 5654 if (comment != nullptr && comment[0] == '\0') 5655 comment = nullptr; 5656 5657 strm.Clear(); 5658 5659 if (mnemonic != nullptr && operands != nullptr && comment != nullptr) 5660 strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment); 5661 else if (mnemonic != nullptr && operands != nullptr) 5662 strm.Printf("%-8s %s", mnemonic, operands); 5663 else if (mnemonic != nullptr) 5664 strm.Printf("%s", mnemonic); 5665 5666 int right_pad = 1; 5667 window.PutCStringTruncated( 5668 right_pad, 5669 strm.GetString().substr(m_first_visible_column).data()); 5670 5671 if (is_pc_line && frame_sp && 5672 frame_sp->GetConcreteFrameIndex() == 0) { 5673 StopInfoSP stop_info_sp; 5674 if (thread) 5675 stop_info_sp = thread->GetStopInfo(); 5676 if (stop_info_sp) { 5677 const char *stop_description = stop_info_sp->GetDescription(); 5678 if (stop_description && stop_description[0]) { 5679 size_t stop_description_len = strlen(stop_description); 5680 int desc_x = window_width - stop_description_len - 16; 5681 if (desc_x - window.GetCursorX() > 0) 5682 window.Printf("%*s", desc_x - window.GetCursorX(), ""); 5683 window.MoveCursor(window_width - stop_description_len - 15, 5684 line_y); 5685 window.PrintfTruncated(1, "<<< Thread %u: %s ", 5686 thread->GetIndexID(), stop_description); 5687 } 5688 } else { 5689 window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); 5690 } 5691 } 5692 if (highlight_attr) 5693 window.AttributeOff(highlight_attr); 5694 } 5695 } 5696 } 5697 return true; // Drawing handled 5698 } 5699 5700 size_t GetNumLines() { 5701 size_t num_lines = GetNumSourceLines(); 5702 if (num_lines == 0) 5703 num_lines = GetNumDisassemblyLines(); 5704 return num_lines; 5705 } 5706 5707 size_t GetNumSourceLines() const { 5708 if (m_file_sp) 5709 return m_file_sp->GetNumLines(); 5710 return 0; 5711 } 5712 5713 size_t GetNumDisassemblyLines() const { 5714 if (m_disassembly_sp) 5715 return m_disassembly_sp->GetInstructionList().GetSize(); 5716 return 0; 5717 } 5718 5719 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 5720 const uint32_t num_visible_lines = NumVisibleLines(); 5721 const size_t num_lines = GetNumLines(); 5722 5723 switch (c) { 5724 case ',': 5725 case KEY_PPAGE: 5726 // Page up key 5727 if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines) 5728 m_first_visible_line -= num_visible_lines; 5729 else 5730 m_first_visible_line = 0; 5731 m_selected_line = m_first_visible_line; 5732 return eKeyHandled; 5733 5734 case '.': 5735 case KEY_NPAGE: 5736 // Page down key 5737 { 5738 if (m_first_visible_line + num_visible_lines < num_lines) 5739 m_first_visible_line += num_visible_lines; 5740 else if (num_lines < num_visible_lines) 5741 m_first_visible_line = 0; 5742 else 5743 m_first_visible_line = num_lines - num_visible_lines; 5744 m_selected_line = m_first_visible_line; 5745 } 5746 return eKeyHandled; 5747 5748 case KEY_UP: 5749 if (m_selected_line > 0) { 5750 m_selected_line--; 5751 if (static_cast<size_t>(m_first_visible_line) > m_selected_line) 5752 m_first_visible_line = m_selected_line; 5753 } 5754 return eKeyHandled; 5755 5756 case KEY_DOWN: 5757 if (m_selected_line + 1 < num_lines) { 5758 m_selected_line++; 5759 if (m_first_visible_line + num_visible_lines < m_selected_line) 5760 m_first_visible_line++; 5761 } 5762 return eKeyHandled; 5763 5764 case KEY_LEFT: 5765 if (m_first_visible_column > 0) 5766 --m_first_visible_column; 5767 return eKeyHandled; 5768 5769 case KEY_RIGHT: 5770 ++m_first_visible_column; 5771 return eKeyHandled; 5772 5773 case '\r': 5774 case '\n': 5775 case KEY_ENTER: 5776 // Set a breakpoint and run to the line using a one shot breakpoint 5777 if (GetNumSourceLines() > 0) { 5778 ExecutionContext exe_ctx = 5779 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5780 if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) { 5781 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 5782 nullptr, // Don't limit the breakpoint to certain modules 5783 m_file_sp->GetFileSpec(), // Source file 5784 m_selected_line + 5785 1, // Source line number (m_selected_line is zero based) 5786 0, // Unspecified column. 5787 0, // No offset 5788 eLazyBoolCalculate, // Check inlines using global setting 5789 eLazyBoolCalculate, // Skip prologue using global setting, 5790 false, // internal 5791 false, // request_hardware 5792 eLazyBoolCalculate); // move_to_nearest_code 5793 // Make breakpoint one shot 5794 bp_sp->GetOptions().SetOneShot(true); 5795 exe_ctx.GetProcessRef().Resume(); 5796 } 5797 } else if (m_selected_line < GetNumDisassemblyLines()) { 5798 const Instruction *inst = m_disassembly_sp->GetInstructionList() 5799 .GetInstructionAtIndex(m_selected_line) 5800 .get(); 5801 ExecutionContext exe_ctx = 5802 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5803 if (exe_ctx.HasTargetScope()) { 5804 Address addr = inst->GetAddress(); 5805 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 5806 addr, // lldb_private::Address 5807 false, // internal 5808 false); // request_hardware 5809 // Make breakpoint one shot 5810 bp_sp->GetOptions().SetOneShot(true); 5811 exe_ctx.GetProcessRef().Resume(); 5812 } 5813 } 5814 return eKeyHandled; 5815 5816 case 'b': // 'b' == toggle breakpoint on currently selected line 5817 ToggleBreakpointOnSelectedLine(); 5818 return eKeyHandled; 5819 5820 case 'D': // 'D' == detach and keep stopped 5821 { 5822 ExecutionContext exe_ctx = 5823 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5824 if (exe_ctx.HasProcessScope()) 5825 exe_ctx.GetProcessRef().Detach(true); 5826 } 5827 return eKeyHandled; 5828 5829 case 'c': 5830 // 'c' == continue 5831 { 5832 ExecutionContext exe_ctx = 5833 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5834 if (exe_ctx.HasProcessScope()) 5835 exe_ctx.GetProcessRef().Resume(); 5836 } 5837 return eKeyHandled; 5838 5839 case 'f': 5840 // 'f' == step out (finish) 5841 { 5842 ExecutionContext exe_ctx = 5843 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5844 if (exe_ctx.HasThreadScope() && 5845 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 5846 exe_ctx.GetThreadRef().StepOut(); 5847 } 5848 } 5849 return eKeyHandled; 5850 5851 case 'n': // 'n' == step over 5852 case 'N': // 'N' == step over instruction 5853 { 5854 ExecutionContext exe_ctx = 5855 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5856 if (exe_ctx.HasThreadScope() && 5857 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 5858 bool source_step = (c == 'n'); 5859 exe_ctx.GetThreadRef().StepOver(source_step); 5860 } 5861 } 5862 return eKeyHandled; 5863 5864 case 's': // 's' == step into 5865 case 'S': // 'S' == step into instruction 5866 { 5867 ExecutionContext exe_ctx = 5868 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5869 if (exe_ctx.HasThreadScope() && 5870 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 5871 bool source_step = (c == 's'); 5872 exe_ctx.GetThreadRef().StepIn(source_step); 5873 } 5874 } 5875 return eKeyHandled; 5876 5877 case 'u': // 'u' == frame up 5878 case 'd': // 'd' == frame down 5879 { 5880 ExecutionContext exe_ctx = 5881 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5882 if (exe_ctx.HasThreadScope()) { 5883 Thread *thread = exe_ctx.GetThreadPtr(); 5884 uint32_t frame_idx = thread->GetSelectedFrameIndex(); 5885 if (frame_idx == UINT32_MAX) 5886 frame_idx = 0; 5887 if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount()) 5888 ++frame_idx; 5889 else if (c == 'd' && frame_idx > 0) 5890 --frame_idx; 5891 if (thread->SetSelectedFrameByIndex(frame_idx, true)) 5892 exe_ctx.SetFrameSP(thread->GetSelectedFrame()); 5893 } 5894 } 5895 return eKeyHandled; 5896 5897 case 'h': 5898 window.CreateHelpSubwindow(); 5899 return eKeyHandled; 5900 5901 default: 5902 break; 5903 } 5904 return eKeyNotHandled; 5905 } 5906 5907 void ToggleBreakpointOnSelectedLine() { 5908 ExecutionContext exe_ctx = 5909 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5910 if (!exe_ctx.HasTargetScope()) 5911 return; 5912 if (GetNumSourceLines() > 0) { 5913 // Source file breakpoint. 5914 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); 5915 const size_t num_bps = bp_list.GetSize(); 5916 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 5917 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 5918 const size_t num_bps_locs = bp_sp->GetNumLocations(); 5919 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 5920 BreakpointLocationSP bp_loc_sp = 5921 bp_sp->GetLocationAtIndex(bp_loc_idx); 5922 LineEntry bp_loc_line_entry; 5923 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( 5924 bp_loc_line_entry)) { 5925 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file && 5926 m_selected_line + 1 == bp_loc_line_entry.line) { 5927 bool removed = 5928 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); 5929 assert(removed); 5930 UNUSED_IF_ASSERT_DISABLED(removed); 5931 return; // Existing breakpoint removed. 5932 } 5933 } 5934 } 5935 } 5936 // No breakpoint found on the location, add it. 5937 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 5938 nullptr, // Don't limit the breakpoint to certain modules 5939 m_file_sp->GetFileSpec(), // Source file 5940 m_selected_line + 5941 1, // Source line number (m_selected_line is zero based) 5942 0, // No column specified. 5943 0, // No offset 5944 eLazyBoolCalculate, // Check inlines using global setting 5945 eLazyBoolCalculate, // Skip prologue using global setting, 5946 false, // internal 5947 false, // request_hardware 5948 eLazyBoolCalculate); // move_to_nearest_code 5949 } else { 5950 // Disassembly breakpoint. 5951 assert(GetNumDisassemblyLines() > 0); 5952 assert(m_selected_line < GetNumDisassemblyLines()); 5953 const Instruction *inst = m_disassembly_sp->GetInstructionList() 5954 .GetInstructionAtIndex(m_selected_line) 5955 .get(); 5956 Address addr = inst->GetAddress(); 5957 // Try to find it. 5958 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); 5959 const size_t num_bps = bp_list.GetSize(); 5960 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 5961 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 5962 const size_t num_bps_locs = bp_sp->GetNumLocations(); 5963 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 5964 BreakpointLocationSP bp_loc_sp = 5965 bp_sp->GetLocationAtIndex(bp_loc_idx); 5966 LineEntry bp_loc_line_entry; 5967 const lldb::addr_t file_addr = 5968 bp_loc_sp->GetAddress().GetFileAddress(); 5969 if (file_addr == addr.GetFileAddress()) { 5970 bool removed = 5971 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); 5972 assert(removed); 5973 UNUSED_IF_ASSERT_DISABLED(removed); 5974 return; // Existing breakpoint removed. 5975 } 5976 } 5977 } 5978 // No breakpoint found on the address, add it. 5979 BreakpointSP bp_sp = 5980 exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address 5981 false, // internal 5982 false); // request_hardware 5983 } 5984 } 5985 5986 protected: 5987 typedef std::set<uint32_t> BreakpointLines; 5988 typedef std::set<lldb::addr_t> BreakpointAddrs; 5989 5990 Debugger &m_debugger; 5991 SymbolContext m_sc; 5992 SourceManager::FileSP m_file_sp; 5993 SymbolContextScope *m_disassembly_scope; 5994 lldb::DisassemblerSP m_disassembly_sp; 5995 AddressRange m_disassembly_range; 5996 StreamString m_title; 5997 lldb::user_id_t m_tid; 5998 int m_line_width; 5999 uint32_t m_selected_line; // The selected line 6000 uint32_t m_pc_line; // The line with the PC 6001 uint32_t m_stop_id; 6002 uint32_t m_frame_idx; 6003 int m_first_visible_line; 6004 int m_first_visible_column; 6005 int m_min_x; 6006 int m_min_y; 6007 int m_max_x; 6008 int m_max_y; 6009 }; 6010 6011 DisplayOptions ValueObjectListDelegate::g_options = {true}; 6012 6013 IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger) 6014 : IOHandler(debugger, IOHandler::Type::Curses) {} 6015 6016 void IOHandlerCursesGUI::Activate() { 6017 IOHandler::Activate(); 6018 if (!m_app_ap) { 6019 m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE()); 6020 6021 // This is both a window and a menu delegate 6022 std::shared_ptr<ApplicationDelegate> app_delegate_sp( 6023 new ApplicationDelegate(*m_app_ap, m_debugger)); 6024 6025 MenuDelegateSP app_menu_delegate_sp = 6026 std::static_pointer_cast<MenuDelegate>(app_delegate_sp); 6027 MenuSP lldb_menu_sp( 6028 new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB)); 6029 MenuSP exit_menuitem_sp( 6030 new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit)); 6031 exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit); 6032 lldb_menu_sp->AddSubmenu(MenuSP(new Menu( 6033 "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout))); 6034 lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 6035 lldb_menu_sp->AddSubmenu(exit_menuitem_sp); 6036 6037 MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2), 6038 ApplicationDelegate::eMenuID_Target)); 6039 target_menu_sp->AddSubmenu(MenuSP(new Menu( 6040 "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate))); 6041 target_menu_sp->AddSubmenu(MenuSP(new Menu( 6042 "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete))); 6043 6044 MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3), 6045 ApplicationDelegate::eMenuID_Process)); 6046 process_menu_sp->AddSubmenu(MenuSP(new Menu( 6047 "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach))); 6048 process_menu_sp->AddSubmenu( 6049 MenuSP(new Menu("Detach and resume", nullptr, 'd', 6050 ApplicationDelegate::eMenuID_ProcessDetachResume))); 6051 process_menu_sp->AddSubmenu( 6052 MenuSP(new Menu("Detach suspended", nullptr, 's', 6053 ApplicationDelegate::eMenuID_ProcessDetachSuspended))); 6054 process_menu_sp->AddSubmenu(MenuSP(new Menu( 6055 "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch))); 6056 process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 6057 process_menu_sp->AddSubmenu( 6058 MenuSP(new Menu("Continue", nullptr, 'c', 6059 ApplicationDelegate::eMenuID_ProcessContinue))); 6060 process_menu_sp->AddSubmenu(MenuSP(new Menu( 6061 "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt))); 6062 process_menu_sp->AddSubmenu(MenuSP(new Menu( 6063 "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill))); 6064 6065 MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4), 6066 ApplicationDelegate::eMenuID_Thread)); 6067 thread_menu_sp->AddSubmenu(MenuSP(new Menu( 6068 "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn))); 6069 thread_menu_sp->AddSubmenu( 6070 MenuSP(new Menu("Step Over", nullptr, 'v', 6071 ApplicationDelegate::eMenuID_ThreadStepOver))); 6072 thread_menu_sp->AddSubmenu(MenuSP(new Menu( 6073 "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut))); 6074 6075 MenuSP view_menu_sp( 6076 new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View)); 6077 view_menu_sp->AddSubmenu( 6078 MenuSP(new Menu("Backtrace", nullptr, 'b', 6079 ApplicationDelegate::eMenuID_ViewBacktrace))); 6080 view_menu_sp->AddSubmenu( 6081 MenuSP(new Menu("Registers", nullptr, 'r', 6082 ApplicationDelegate::eMenuID_ViewRegisters))); 6083 view_menu_sp->AddSubmenu(MenuSP(new Menu( 6084 "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource))); 6085 view_menu_sp->AddSubmenu( 6086 MenuSP(new Menu("Variables", nullptr, 'v', 6087 ApplicationDelegate::eMenuID_ViewVariables))); 6088 6089 MenuSP help_menu_sp( 6090 new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help)); 6091 help_menu_sp->AddSubmenu(MenuSP(new Menu( 6092 "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp))); 6093 6094 m_app_ap->Initialize(); 6095 WindowSP &main_window_sp = m_app_ap->GetMainWindow(); 6096 6097 MenuSP menubar_sp(new Menu(Menu::Type::Bar)); 6098 menubar_sp->AddSubmenu(lldb_menu_sp); 6099 menubar_sp->AddSubmenu(target_menu_sp); 6100 menubar_sp->AddSubmenu(process_menu_sp); 6101 menubar_sp->AddSubmenu(thread_menu_sp); 6102 menubar_sp->AddSubmenu(view_menu_sp); 6103 menubar_sp->AddSubmenu(help_menu_sp); 6104 menubar_sp->SetDelegate(app_menu_delegate_sp); 6105 6106 Rect content_bounds = main_window_sp->GetFrame(); 6107 Rect menubar_bounds = content_bounds.MakeMenuBar(); 6108 Rect status_bounds = content_bounds.MakeStatusBar(); 6109 Rect source_bounds; 6110 Rect variables_bounds; 6111 Rect threads_bounds; 6112 Rect source_variables_bounds; 6113 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, 6114 threads_bounds); 6115 source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds, 6116 variables_bounds); 6117 6118 WindowSP menubar_window_sp = 6119 main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false); 6120 // Let the menubar get keys if the active window doesn't handle the keys 6121 // that are typed so it can respond to menubar key presses. 6122 menubar_window_sp->SetCanBeActive( 6123 false); // Don't let the menubar become the active window 6124 menubar_window_sp->SetDelegate(menubar_sp); 6125 6126 WindowSP source_window_sp( 6127 main_window_sp->CreateSubWindow("Source", source_bounds, true)); 6128 WindowSP variables_window_sp( 6129 main_window_sp->CreateSubWindow("Variables", variables_bounds, false)); 6130 WindowSP threads_window_sp( 6131 main_window_sp->CreateSubWindow("Threads", threads_bounds, false)); 6132 WindowSP status_window_sp( 6133 main_window_sp->CreateSubWindow("Status", status_bounds, false)); 6134 status_window_sp->SetCanBeActive( 6135 false); // Don't let the status bar become the active window 6136 main_window_sp->SetDelegate( 6137 std::static_pointer_cast<WindowDelegate>(app_delegate_sp)); 6138 source_window_sp->SetDelegate( 6139 WindowDelegateSP(new SourceFileWindowDelegate(m_debugger))); 6140 variables_window_sp->SetDelegate( 6141 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 6142 TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger)); 6143 threads_window_sp->SetDelegate(WindowDelegateSP( 6144 new TreeWindowDelegate(m_debugger, thread_delegate_sp))); 6145 status_window_sp->SetDelegate( 6146 WindowDelegateSP(new StatusBarWindowDelegate(m_debugger))); 6147 6148 // Show the main help window once the first time the curses GUI is launched 6149 static bool g_showed_help = false; 6150 if (!g_showed_help) { 6151 g_showed_help = true; 6152 main_window_sp->CreateHelpSubwindow(); 6153 } 6154 6155 // All colors with black background. 6156 init_pair(1, COLOR_BLACK, COLOR_BLACK); 6157 init_pair(2, COLOR_RED, COLOR_BLACK); 6158 init_pair(3, COLOR_GREEN, COLOR_BLACK); 6159 init_pair(4, COLOR_YELLOW, COLOR_BLACK); 6160 init_pair(5, COLOR_BLUE, COLOR_BLACK); 6161 init_pair(6, COLOR_MAGENTA, COLOR_BLACK); 6162 init_pair(7, COLOR_CYAN, COLOR_BLACK); 6163 init_pair(8, COLOR_WHITE, COLOR_BLACK); 6164 // All colors with blue background. 6165 init_pair(9, COLOR_BLACK, COLOR_BLUE); 6166 init_pair(10, COLOR_RED, COLOR_BLUE); 6167 init_pair(11, COLOR_GREEN, COLOR_BLUE); 6168 init_pair(12, COLOR_YELLOW, COLOR_BLUE); 6169 init_pair(13, COLOR_BLUE, COLOR_BLUE); 6170 init_pair(14, COLOR_MAGENTA, COLOR_BLUE); 6171 init_pair(15, COLOR_CYAN, COLOR_BLUE); 6172 init_pair(16, COLOR_WHITE, COLOR_BLUE); 6173 // These must match the order in the color indexes enum. 6174 init_pair(17, COLOR_BLACK, COLOR_WHITE); 6175 init_pair(18, COLOR_MAGENTA, COLOR_WHITE); 6176 static_assert(LastColorPairIndex == 18, "Color indexes do not match."); 6177 6178 define_key("\033[Z", KEY_SHIFT_TAB); 6179 } 6180 } 6181 6182 void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); } 6183 6184 void IOHandlerCursesGUI::Run() { 6185 m_app_ap->Run(m_debugger); 6186 SetIsDone(true); 6187 } 6188 6189 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default; 6190 6191 void IOHandlerCursesGUI::Cancel() {} 6192 6193 bool IOHandlerCursesGUI::Interrupt() { return false; } 6194 6195 void IOHandlerCursesGUI::GotEOF() {} 6196 6197 void IOHandlerCursesGUI::TerminalSizeChanged() { 6198 m_app_ap->TerminalSizeChanged(); 6199 } 6200 6201 #endif // LLDB_ENABLE_CURSES 6202