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 BooleanFieldDelegate : public FieldDelegate { 1378 public: 1379 BooleanFieldDelegate(const char *label, bool content) 1380 : m_label(label), m_content(content) {} 1381 1382 // Boolean fields are drawn as checkboxes. 1383 // 1384 // [X] Label or [ ] Label 1385 1386 // Boolean fields are have a single line. 1387 int FieldDelegateGetHeight() override { return 1; } 1388 1389 void FieldDelegateDraw(SubPad &surface, bool is_selected) override { 1390 surface.MoveCursor(0, 0); 1391 surface.PutChar('['); 1392 if (is_selected) 1393 surface.AttributeOn(A_REVERSE); 1394 surface.PutChar(m_content ? ACS_DIAMOND : ' '); 1395 if (is_selected) 1396 surface.AttributeOff(A_REVERSE); 1397 surface.PutChar(']'); 1398 surface.PutChar(' '); 1399 surface.PutCString(m_label.c_str()); 1400 } 1401 1402 void ToggleContent() { m_content = !m_content; } 1403 1404 void SetContentToTrue() { m_content = true; } 1405 1406 void SetContentToFalse() { m_content = false; } 1407 1408 HandleCharResult FieldDelegateHandleChar(int key) override { 1409 switch (key) { 1410 case 't': 1411 case '1': 1412 SetContentToTrue(); 1413 return eKeyHandled; 1414 case 'f': 1415 case '0': 1416 SetContentToFalse(); 1417 return eKeyHandled; 1418 case ' ': 1419 case '\r': 1420 case '\n': 1421 case KEY_ENTER: 1422 ToggleContent(); 1423 return eKeyHandled; 1424 default: 1425 break; 1426 } 1427 return eKeyNotHandled; 1428 } 1429 1430 // Returns the boolean content of the field. 1431 bool GetBoolean() { return m_content; } 1432 1433 protected: 1434 std::string m_label; 1435 bool m_content; 1436 }; 1437 1438 class ChoicesFieldDelegate : public FieldDelegate { 1439 public: 1440 ChoicesFieldDelegate(const char *label, int number_of_visible_choices, 1441 std::vector<std::string> choices) 1442 : m_label(label), m_number_of_visible_choices(number_of_visible_choices), 1443 m_choices(choices), m_choice(0), m_first_visibile_choice(0) {} 1444 1445 // Choices fields are drawn as titles boxses of a number of visible choices. 1446 // The rest of the choices become visible as the user scroll. The selected 1447 // choice is denoted by a diamond as the first character. 1448 // 1449 // __[Label]___________ 1450 // |-Choice 1 | 1451 // | Choice 2 | 1452 // | Choice 3 | 1453 // |__________________| 1454 1455 // Choices field have two border characters plus the number of visible 1456 // choices. 1457 int FieldDelegateGetHeight() override { 1458 return m_number_of_visible_choices + 2; 1459 } 1460 1461 int GetNumberOfChoices() { return m_choices.size(); } 1462 1463 // Get the index of the last visible choice. 1464 int GetLastVisibleChoice() { 1465 int index = m_first_visibile_choice + m_number_of_visible_choices; 1466 return std::min(index, GetNumberOfChoices()) - 1; 1467 } 1468 1469 void DrawContent(SubPad &surface, bool is_selected) { 1470 int choices_to_draw = GetLastVisibleChoice() - m_first_visibile_choice + 1; 1471 for (int i = 0; i < choices_to_draw; i++) { 1472 surface.MoveCursor(0, i); 1473 int current_choice = m_first_visibile_choice + i; 1474 const char *text = m_choices[current_choice].c_str(); 1475 bool highlight = is_selected && current_choice == m_choice; 1476 if (highlight) 1477 surface.AttributeOn(A_REVERSE); 1478 surface.PutChar(current_choice == m_choice ? ACS_DIAMOND : ' '); 1479 surface.PutCString(text); 1480 if (highlight) 1481 surface.AttributeOff(A_REVERSE); 1482 } 1483 } 1484 1485 void FieldDelegateDraw(SubPad &surface, bool is_selected) override { 1486 UpdateScrolling(); 1487 1488 surface.TitledBox(m_label.c_str()); 1489 1490 Rect content_bounds = surface.GetFrame(); 1491 content_bounds.Inset(1, 1); 1492 SubPad content_surface = SubPad(surface, content_bounds); 1493 1494 DrawContent(content_surface, is_selected); 1495 } 1496 1497 void SelectPrevious() { 1498 if (m_choice > 0) 1499 m_choice--; 1500 } 1501 1502 void SelectNext() { 1503 if (m_choice < GetNumberOfChoices() - 1) 1504 m_choice++; 1505 } 1506 1507 void UpdateScrolling() { 1508 if (m_choice > GetLastVisibleChoice()) { 1509 m_first_visibile_choice = m_choice - (m_number_of_visible_choices - 1); 1510 return; 1511 } 1512 1513 if (m_choice < m_first_visibile_choice) 1514 m_first_visibile_choice = m_choice; 1515 } 1516 1517 HandleCharResult FieldDelegateHandleChar(int key) override { 1518 switch (key) { 1519 case KEY_UP: 1520 SelectPrevious(); 1521 return eKeyHandled; 1522 case KEY_DOWN: 1523 SelectNext(); 1524 return eKeyHandled; 1525 default: 1526 break; 1527 } 1528 return eKeyNotHandled; 1529 } 1530 1531 // Returns the content of the choice as a string. 1532 std::string GetChoiceContent() { return m_choices[m_choice]; } 1533 1534 // Returns the index of the choice. 1535 int GetChoice() { return m_choice; } 1536 1537 void SetChoice(const std::string &choice) { 1538 for (int i = 0; i < GetNumberOfChoices(); i++) { 1539 if (choice == m_choices[i]) { 1540 m_choice = i; 1541 return; 1542 } 1543 } 1544 } 1545 1546 protected: 1547 std::string m_label; 1548 int m_number_of_visible_choices; 1549 std::vector<std::string> m_choices; 1550 // The index of the selected choice. 1551 int m_choice; 1552 // The index of the first visible choice in the field. 1553 int m_first_visibile_choice; 1554 }; 1555 1556 class PlatformPluginFieldDelegate : public ChoicesFieldDelegate { 1557 public: 1558 PlatformPluginFieldDelegate(Debugger &debugger) 1559 : ChoicesFieldDelegate("Platform Plugin", 3, GetPossiblePluginNames()) { 1560 PlatformSP platform_sp = debugger.GetPlatformList().GetSelectedPlatform(); 1561 if (platform_sp) 1562 SetChoice(platform_sp->GetName().AsCString()); 1563 } 1564 1565 std::vector<std::string> GetPossiblePluginNames() { 1566 std::vector<std::string> names; 1567 size_t i = 0; 1568 while (auto name = PluginManager::GetPlatformPluginNameAtIndex(i++)) 1569 names.push_back(name); 1570 return names; 1571 } 1572 1573 std::string GetPluginName() { 1574 std::string plugin_name = GetChoiceContent(); 1575 return plugin_name; 1576 } 1577 }; 1578 1579 class ProcessPluginFieldDelegate : public ChoicesFieldDelegate { 1580 public: 1581 ProcessPluginFieldDelegate() 1582 : ChoicesFieldDelegate("Process Plugin", 3, GetPossiblePluginNames()) {} 1583 1584 std::vector<std::string> GetPossiblePluginNames() { 1585 std::vector<std::string> names; 1586 names.push_back("<default>"); 1587 1588 size_t i = 0; 1589 while (auto name = PluginManager::GetProcessPluginNameAtIndex(i++)) 1590 names.push_back(name); 1591 return names; 1592 } 1593 1594 std::string GetPluginName() { 1595 std::string plugin_name = GetChoiceContent(); 1596 if (plugin_name == "<default>") 1597 return ""; 1598 return plugin_name; 1599 } 1600 }; 1601 1602 template <class T> class ListFieldDelegate : public FieldDelegate { 1603 public: 1604 ListFieldDelegate(const char *label, T default_field) 1605 : m_label(label), m_default_field(default_field), m_selection_index(0), 1606 m_selection_type(SelectionType::NewButton) {} 1607 1608 // Signify which element is selected. If a field or a remove button is 1609 // selected, then m_selection_index signifies the particular field that 1610 // is selected or the field that the remove button belongs to. 1611 enum class SelectionType { Field, RemoveButton, NewButton }; 1612 1613 // A List field is drawn as a titled box of a number of other fields of the 1614 // same type. Each field has a Remove button next to it that removes the 1615 // corresponding field. Finally, the last line contains a New button to add a 1616 // new field. 1617 // 1618 // __[Label]___________ 1619 // | Field 0 [Remove] | 1620 // | Field 1 [Remove] | 1621 // | Field 2 [Remove] | 1622 // | [New] | 1623 // |__________________| 1624 1625 // List fields have two lines for border characters, 1 line for the New 1626 // button, and the total height of the available fields. 1627 int FieldDelegateGetHeight() override { 1628 // 2 border characters. 1629 int height = 2; 1630 // Total height of the fields. 1631 for (int i = 0; i < GetNumberOfFields(); i++) { 1632 height += m_fields[i].FieldDelegateGetHeight(); 1633 } 1634 // A line for the New button. 1635 height++; 1636 return height; 1637 } 1638 1639 ScrollContext FieldDelegateGetScrollContext() override { 1640 int height = FieldDelegateGetHeight(); 1641 if (m_selection_type == SelectionType::NewButton) 1642 return ScrollContext(height - 2, height - 1); 1643 1644 FieldDelegate &field = m_fields[m_selection_index]; 1645 ScrollContext context = field.FieldDelegateGetScrollContext(); 1646 1647 // Start at 1 because of the top border. 1648 int offset = 1; 1649 for (int i = 0; i < m_selection_index; i++) { 1650 offset += m_fields[i].FieldDelegateGetHeight(); 1651 } 1652 context.Offset(offset); 1653 1654 // If the scroll context is touching the top border, include it in the 1655 // context to show the label. 1656 if (context.start == 1) 1657 context.start--; 1658 1659 // If the scroll context is touching the new button, include it as well as 1660 // the bottom border in the context. 1661 if (context.end == height - 3) 1662 context.end += 2; 1663 1664 return context; 1665 } 1666 1667 void DrawRemoveButton(SubPad &surface, int highlight) { 1668 surface.MoveCursor(1, surface.GetHeight() / 2); 1669 if (highlight) 1670 surface.AttributeOn(A_REVERSE); 1671 surface.PutCString("[Remove]"); 1672 if (highlight) 1673 surface.AttributeOff(A_REVERSE); 1674 } 1675 1676 void DrawFields(SubPad &surface, bool is_selected) { 1677 int line = 0; 1678 int width = surface.GetWidth(); 1679 for (int i = 0; i < GetNumberOfFields(); i++) { 1680 int height = m_fields[i].FieldDelegateGetHeight(); 1681 Rect bounds = Rect(Point(0, line), Size(width, height)); 1682 Rect field_bounds, remove_button_bounds; 1683 bounds.VerticalSplit(bounds.size.width - sizeof(" [Remove]"), 1684 field_bounds, remove_button_bounds); 1685 SubPad field_surface = SubPad(surface, field_bounds); 1686 SubPad remove_button_surface = SubPad(surface, remove_button_bounds); 1687 1688 bool is_element_selected = m_selection_index == i && is_selected; 1689 bool is_field_selected = 1690 is_element_selected && m_selection_type == SelectionType::Field; 1691 bool is_remove_button_selected = 1692 is_element_selected && 1693 m_selection_type == SelectionType::RemoveButton; 1694 m_fields[i].FieldDelegateDraw(field_surface, is_field_selected); 1695 DrawRemoveButton(remove_button_surface, is_remove_button_selected); 1696 1697 line += height; 1698 } 1699 } 1700 1701 void DrawNewButton(SubPad &surface, bool is_selected) { 1702 const char *button_text = "[New]"; 1703 int x = (surface.GetWidth() - sizeof(button_text) - 1) / 2; 1704 surface.MoveCursor(x, 0); 1705 bool highlight = 1706 is_selected && m_selection_type == SelectionType::NewButton; 1707 if (highlight) 1708 surface.AttributeOn(A_REVERSE); 1709 surface.PutCString(button_text); 1710 if (highlight) 1711 surface.AttributeOff(A_REVERSE); 1712 } 1713 1714 void FieldDelegateDraw(SubPad &surface, bool is_selected) override { 1715 surface.TitledBox(m_label.c_str()); 1716 1717 Rect content_bounds = surface.GetFrame(); 1718 content_bounds.Inset(1, 1); 1719 Rect fields_bounds, new_button_bounds; 1720 content_bounds.HorizontalSplit(content_bounds.size.height - 1, 1721 fields_bounds, new_button_bounds); 1722 SubPad fields_surface = SubPad(surface, fields_bounds); 1723 SubPad new_button_surface = SubPad(surface, new_button_bounds); 1724 1725 DrawFields(fields_surface, is_selected); 1726 DrawNewButton(new_button_surface, is_selected); 1727 } 1728 1729 void AddNewField() { 1730 m_fields.push_back(m_default_field); 1731 m_selection_index = GetNumberOfFields() - 1; 1732 m_selection_type = SelectionType::Field; 1733 FieldDelegate &field = m_fields[m_selection_index]; 1734 field.FieldDelegateSelectFirstElement(); 1735 } 1736 1737 void RemoveField() { 1738 m_fields.erase(m_fields.begin() + m_selection_index); 1739 if (m_selection_index != 0) 1740 m_selection_index--; 1741 1742 if (GetNumberOfFields() > 0) { 1743 m_selection_type = SelectionType::Field; 1744 FieldDelegate &field = m_fields[m_selection_index]; 1745 field.FieldDelegateSelectFirstElement(); 1746 } else 1747 m_selection_type = SelectionType::NewButton; 1748 } 1749 1750 HandleCharResult SelectNext(int key) { 1751 if (m_selection_type == SelectionType::NewButton) 1752 return eKeyNotHandled; 1753 1754 if (m_selection_type == SelectionType::RemoveButton) { 1755 if (m_selection_index == GetNumberOfFields() - 1) { 1756 m_selection_type = SelectionType::NewButton; 1757 return eKeyHandled; 1758 } 1759 m_selection_index++; 1760 m_selection_type = SelectionType::Field; 1761 FieldDelegate &next_field = m_fields[m_selection_index]; 1762 next_field.FieldDelegateSelectFirstElement(); 1763 return eKeyHandled; 1764 } 1765 1766 FieldDelegate &field = m_fields[m_selection_index]; 1767 if (!field.FieldDelegateOnLastOrOnlyElement()) { 1768 return field.FieldDelegateHandleChar(key); 1769 } 1770 1771 field.FieldDelegateExitCallback(); 1772 1773 m_selection_type = SelectionType::RemoveButton; 1774 return eKeyHandled; 1775 } 1776 1777 HandleCharResult SelectPrevious(int key) { 1778 if (FieldDelegateOnFirstOrOnlyElement()) 1779 return eKeyNotHandled; 1780 1781 if (m_selection_type == SelectionType::RemoveButton) { 1782 m_selection_type = SelectionType::Field; 1783 FieldDelegate &field = m_fields[m_selection_index]; 1784 field.FieldDelegateSelectLastElement(); 1785 return eKeyHandled; 1786 } 1787 1788 if (m_selection_type == SelectionType::NewButton) { 1789 m_selection_type = SelectionType::RemoveButton; 1790 m_selection_index = GetNumberOfFields() - 1; 1791 return eKeyHandled; 1792 } 1793 1794 FieldDelegate &field = m_fields[m_selection_index]; 1795 if (!field.FieldDelegateOnFirstOrOnlyElement()) { 1796 return field.FieldDelegateHandleChar(key); 1797 } 1798 1799 field.FieldDelegateExitCallback(); 1800 1801 m_selection_type = SelectionType::RemoveButton; 1802 m_selection_index--; 1803 return eKeyHandled; 1804 } 1805 1806 HandleCharResult FieldDelegateHandleChar(int key) override { 1807 switch (key) { 1808 case '\r': 1809 case '\n': 1810 case KEY_ENTER: 1811 switch (m_selection_type) { 1812 case SelectionType::NewButton: 1813 AddNewField(); 1814 return eKeyHandled; 1815 case SelectionType::RemoveButton: 1816 RemoveField(); 1817 return eKeyHandled; 1818 default: 1819 break; 1820 } 1821 break; 1822 case '\t': 1823 SelectNext(key); 1824 return eKeyHandled; 1825 case KEY_SHIFT_TAB: 1826 SelectPrevious(key); 1827 return eKeyHandled; 1828 default: 1829 break; 1830 } 1831 1832 // If the key wasn't handled and one of the fields is selected, pass the key 1833 // to that field. 1834 if (m_selection_type == SelectionType::Field) { 1835 return m_fields[m_selection_index].FieldDelegateHandleChar(key); 1836 } 1837 1838 return eKeyNotHandled; 1839 } 1840 1841 bool FieldDelegateOnLastOrOnlyElement() override { 1842 if (m_selection_type == SelectionType::NewButton) { 1843 return true; 1844 } 1845 return false; 1846 } 1847 1848 bool FieldDelegateOnFirstOrOnlyElement() override { 1849 if (m_selection_type == SelectionType::NewButton && 1850 GetNumberOfFields() == 0) 1851 return true; 1852 1853 if (m_selection_type == SelectionType::Field && m_selection_index == 0) { 1854 FieldDelegate &field = m_fields[m_selection_index]; 1855 return field.FieldDelegateOnFirstOrOnlyElement(); 1856 } 1857 1858 return false; 1859 } 1860 1861 void FieldDelegateSelectFirstElement() override { 1862 if (GetNumberOfFields() == 0) { 1863 m_selection_type = SelectionType::NewButton; 1864 return; 1865 } 1866 1867 m_selection_type = SelectionType::Field; 1868 m_selection_index = 0; 1869 } 1870 1871 void FieldDelegateSelectLastElement() override { 1872 m_selection_type = SelectionType::NewButton; 1873 return; 1874 } 1875 1876 int GetNumberOfFields() { return m_fields.size(); } 1877 1878 // Returns the form delegate at the current index. 1879 T &GetField(int index) { return m_fields[index]; } 1880 1881 protected: 1882 std::string m_label; 1883 // The default field delegate instance from which new field delegates will be 1884 // created though a copy. 1885 T m_default_field; 1886 std::vector<T> m_fields; 1887 int m_selection_index; 1888 // See SelectionType class enum. 1889 SelectionType m_selection_type; 1890 }; 1891 1892 class FormAction { 1893 public: 1894 FormAction(const char *label, std::function<void(Window &)> action) 1895 : m_action(action) { 1896 if (label) 1897 m_label = label; 1898 } 1899 1900 // Draw a centered [Label]. 1901 void Draw(SubPad &surface, bool is_selected) { 1902 int x = (surface.GetWidth() - m_label.length()) / 2; 1903 surface.MoveCursor(x, 0); 1904 if (is_selected) 1905 surface.AttributeOn(A_REVERSE); 1906 surface.PutChar('['); 1907 surface.PutCString(m_label.c_str()); 1908 surface.PutChar(']'); 1909 if (is_selected) 1910 surface.AttributeOff(A_REVERSE); 1911 } 1912 1913 void Execute(Window &window) { m_action(window); } 1914 1915 const std::string &GetLabel() { return m_label; } 1916 1917 protected: 1918 std::string m_label; 1919 std::function<void(Window &)> m_action; 1920 }; 1921 1922 class FormDelegate { 1923 public: 1924 FormDelegate() {} 1925 1926 virtual ~FormDelegate() = default; 1927 1928 virtual std::string GetName() = 0; 1929 1930 virtual void UpdateFieldsVisibility() { return; } 1931 1932 FieldDelegate *GetField(uint32_t field_index) { 1933 if (field_index < m_fields.size()) 1934 return m_fields[field_index].get(); 1935 return nullptr; 1936 } 1937 1938 FormAction &GetAction(int action_index) { return m_actions[action_index]; } 1939 1940 int GetNumberOfFields() { return m_fields.size(); } 1941 1942 int GetNumberOfActions() { return m_actions.size(); } 1943 1944 bool HasError() { return !m_error.empty(); } 1945 1946 void ClearError() { m_error.clear(); } 1947 1948 const std::string &GetError() { return m_error; } 1949 1950 void SetError(const char *error) { m_error = error; } 1951 1952 // If all fields are valid, true is returned. Otherwise, an error message is 1953 // set and false is returned. This method is usually called at the start of an 1954 // action that requires valid fields. 1955 bool CheckFieldsValidity() { 1956 for (int i = 0; i < GetNumberOfFields(); i++) { 1957 if (GetField(i)->FieldDelegateHasError()) { 1958 SetError("Some fields are invalid!"); 1959 return false; 1960 } 1961 } 1962 return true; 1963 } 1964 1965 // Factory methods to create and add fields of specific types. 1966 1967 TextFieldDelegate *AddTextField(const char *label, const char *content, 1968 bool required) { 1969 TextFieldDelegate *delegate = 1970 new TextFieldDelegate(label, content, required); 1971 m_fields.push_back(FieldDelegateUP(delegate)); 1972 return delegate; 1973 } 1974 1975 FileFieldDelegate *AddFileField(const char *label, const char *content, 1976 bool need_to_exist, bool required) { 1977 FileFieldDelegate *delegate = 1978 new FileFieldDelegate(label, content, need_to_exist, required); 1979 m_fields.push_back(FieldDelegateUP(delegate)); 1980 return delegate; 1981 } 1982 1983 DirectoryFieldDelegate *AddDirectoryField(const char *label, 1984 const char *content, 1985 bool need_to_exist, bool required) { 1986 DirectoryFieldDelegate *delegate = 1987 new DirectoryFieldDelegate(label, content, need_to_exist, required); 1988 m_fields.push_back(FieldDelegateUP(delegate)); 1989 return delegate; 1990 } 1991 1992 IntegerFieldDelegate *AddIntegerField(const char *label, int content, 1993 bool required) { 1994 IntegerFieldDelegate *delegate = 1995 new IntegerFieldDelegate(label, content, required); 1996 m_fields.push_back(FieldDelegateUP(delegate)); 1997 return delegate; 1998 } 1999 2000 BooleanFieldDelegate *AddBooleanField(const char *label, bool content) { 2001 BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content); 2002 m_fields.push_back(FieldDelegateUP(delegate)); 2003 return delegate; 2004 } 2005 2006 ChoicesFieldDelegate *AddChoicesField(const char *label, int height, 2007 std::vector<std::string> choices) { 2008 ChoicesFieldDelegate *delegate = 2009 new ChoicesFieldDelegate(label, height, choices); 2010 m_fields.push_back(FieldDelegateUP(delegate)); 2011 return delegate; 2012 } 2013 2014 PlatformPluginFieldDelegate *AddPlatformPluginField(Debugger &debugger) { 2015 PlatformPluginFieldDelegate *delegate = 2016 new PlatformPluginFieldDelegate(debugger); 2017 m_fields.push_back(FieldDelegateUP(delegate)); 2018 return delegate; 2019 } 2020 2021 ProcessPluginFieldDelegate *AddProcessPluginField() { 2022 ProcessPluginFieldDelegate *delegate = new ProcessPluginFieldDelegate(); 2023 m_fields.push_back(FieldDelegateUP(delegate)); 2024 return delegate; 2025 } 2026 2027 template <class T> 2028 ListFieldDelegate<T> *AddListField(const char *label, T default_field) { 2029 ListFieldDelegate<T> *delegate = 2030 new ListFieldDelegate<T>(label, default_field); 2031 m_fields.push_back(FieldDelegateUP(delegate)); 2032 return delegate; 2033 } 2034 2035 // Factory methods for adding actions. 2036 2037 void AddAction(const char *label, std::function<void(Window &)> action) { 2038 m_actions.push_back(FormAction(label, action)); 2039 } 2040 2041 protected: 2042 std::vector<FieldDelegateUP> m_fields; 2043 std::vector<FormAction> m_actions; 2044 // Optional error message. If empty, form is considered to have no error. 2045 std::string m_error; 2046 }; 2047 2048 typedef std::shared_ptr<FormDelegate> FormDelegateSP; 2049 2050 class FormWindowDelegate : public WindowDelegate { 2051 public: 2052 FormWindowDelegate(FormDelegateSP &delegate_sp) 2053 : m_delegate_sp(delegate_sp), m_selection_index(0), 2054 m_first_visible_line(0) { 2055 assert(m_delegate_sp->GetNumberOfActions() > 0); 2056 if (m_delegate_sp->GetNumberOfFields() > 0) 2057 m_selection_type = SelectionType::Field; 2058 else 2059 m_selection_type = SelectionType::Action; 2060 } 2061 2062 // Signify which element is selected. If a field or an action is selected, 2063 // then m_selection_index signifies the particular field or action that is 2064 // selected. 2065 enum class SelectionType { Field, Action }; 2066 2067 // A form window is padded by one character from all sides. First, if an error 2068 // message exists, it is drawn followed by a separator. Then one or more 2069 // fields are drawn. Finally, all available actions are drawn on a single 2070 // line. 2071 // 2072 // ___<Form Name>_________________________________________________ 2073 // | | 2074 // | - Error message if it exists. | 2075 // |-------------------------------------------------------------| 2076 // | Form elements here. | 2077 // | Form actions here. | 2078 // | | 2079 // |______________________________________[Press Esc to cancel]__| 2080 // 2081 2082 // One line for the error and another for the horizontal line. 2083 int GetErrorHeight() { 2084 if (m_delegate_sp->HasError()) 2085 return 2; 2086 return 0; 2087 } 2088 2089 // Actions span a single line. 2090 int GetActionsHeight() { 2091 if (m_delegate_sp->GetNumberOfActions() > 0) 2092 return 1; 2093 return 0; 2094 } 2095 2096 // Get the total number of needed lines to draw the contents. 2097 int GetContentHeight() { 2098 int height = 0; 2099 height += GetErrorHeight(); 2100 for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) { 2101 if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible()) 2102 continue; 2103 height += m_delegate_sp->GetField(i)->FieldDelegateGetHeight(); 2104 } 2105 height += GetActionsHeight(); 2106 return height; 2107 } 2108 2109 ScrollContext GetScrollContext() { 2110 if (m_selection_type == SelectionType::Action) 2111 return ScrollContext(GetContentHeight() - 1); 2112 2113 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); 2114 ScrollContext context = field->FieldDelegateGetScrollContext(); 2115 2116 int offset = GetErrorHeight(); 2117 for (int i = 0; i < m_selection_index; i++) { 2118 if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible()) 2119 continue; 2120 offset += m_delegate_sp->GetField(i)->FieldDelegateGetHeight(); 2121 } 2122 context.Offset(offset); 2123 2124 // If the context is touching the error, include the error in the context as 2125 // well. 2126 if (context.start == GetErrorHeight()) 2127 context.start = 0; 2128 2129 return context; 2130 } 2131 2132 void UpdateScrolling(DerivedWindow &surface) { 2133 ScrollContext context = GetScrollContext(); 2134 int content_height = GetContentHeight(); 2135 int surface_height = surface.GetHeight(); 2136 int visible_height = std::min(content_height, surface_height); 2137 int last_visible_line = m_first_visible_line + visible_height - 1; 2138 2139 // If the last visible line is bigger than the content, then it is invalid 2140 // and needs to be set to the last line in the content. This can happen when 2141 // a field has shrunk in height. 2142 if (last_visible_line > content_height - 1) { 2143 m_first_visible_line = content_height - visible_height; 2144 } 2145 2146 if (context.start < m_first_visible_line) { 2147 m_first_visible_line = context.start; 2148 return; 2149 } 2150 2151 if (context.end > last_visible_line) { 2152 m_first_visible_line = context.end - visible_height + 1; 2153 } 2154 } 2155 2156 void DrawError(SubPad &surface) { 2157 if (!m_delegate_sp->HasError()) 2158 return; 2159 surface.MoveCursor(0, 0); 2160 surface.AttributeOn(COLOR_PAIR(RedOnBlack)); 2161 surface.PutChar(ACS_DIAMOND); 2162 surface.PutChar(' '); 2163 surface.PutCStringTruncated(1, m_delegate_sp->GetError().c_str()); 2164 surface.AttributeOff(COLOR_PAIR(RedOnBlack)); 2165 2166 surface.MoveCursor(0, 1); 2167 surface.HorizontalLine(surface.GetWidth()); 2168 } 2169 2170 void DrawFields(SubPad &surface) { 2171 int line = 0; 2172 int width = surface.GetWidth(); 2173 bool a_field_is_selected = m_selection_type == SelectionType::Field; 2174 for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) { 2175 FieldDelegate *field = m_delegate_sp->GetField(i); 2176 if (!field->FieldDelegateIsVisible()) 2177 continue; 2178 bool is_field_selected = a_field_is_selected && m_selection_index == i; 2179 int height = field->FieldDelegateGetHeight(); 2180 Rect bounds = Rect(Point(0, line), Size(width, height)); 2181 SubPad field_surface = SubPad(surface, bounds); 2182 field->FieldDelegateDraw(field_surface, is_field_selected); 2183 line += height; 2184 } 2185 } 2186 2187 void DrawActions(SubPad &surface) { 2188 int number_of_actions = m_delegate_sp->GetNumberOfActions(); 2189 int width = surface.GetWidth() / number_of_actions; 2190 bool an_action_is_selected = m_selection_type == SelectionType::Action; 2191 int x = 0; 2192 for (int i = 0; i < number_of_actions; i++) { 2193 bool is_action_selected = an_action_is_selected && m_selection_index == i; 2194 FormAction &action = m_delegate_sp->GetAction(i); 2195 Rect bounds = Rect(Point(x, 0), Size(width, 1)); 2196 SubPad action_surface = SubPad(surface, bounds); 2197 action.Draw(action_surface, is_action_selected); 2198 x += width; 2199 } 2200 } 2201 2202 void DrawElements(SubPad &surface) { 2203 Rect frame = surface.GetFrame(); 2204 Rect fields_bounds, actions_bounds; 2205 frame.HorizontalSplit(surface.GetHeight() - GetActionsHeight(), 2206 fields_bounds, actions_bounds); 2207 SubPad fields_surface = SubPad(surface, fields_bounds); 2208 SubPad actions_surface = SubPad(surface, actions_bounds); 2209 2210 DrawFields(fields_surface); 2211 DrawActions(actions_surface); 2212 } 2213 2214 // Contents are first drawn on a pad. Then a subset of that pad is copied to 2215 // the derived window starting at the first visible line. This essentially 2216 // provides scrolling functionality. 2217 void DrawContent(DerivedWindow &surface) { 2218 UpdateScrolling(surface); 2219 2220 int width = surface.GetWidth(); 2221 int height = GetContentHeight(); 2222 Pad pad = Pad(Size(width, height)); 2223 2224 Rect frame = pad.GetFrame(); 2225 Rect error_bounds, elements_bounds; 2226 frame.HorizontalSplit(GetErrorHeight(), error_bounds, elements_bounds); 2227 SubPad error_surface = SubPad(pad, error_bounds); 2228 SubPad elements_surface = SubPad(pad, elements_bounds); 2229 2230 DrawError(error_surface); 2231 DrawElements(elements_surface); 2232 2233 int copy_height = std::min(surface.GetHeight(), pad.GetHeight()); 2234 pad.CopyToSurface(surface, Point(0, m_first_visible_line), Point(), 2235 Size(width, copy_height)); 2236 } 2237 2238 bool WindowDelegateDraw(Window &window, bool force) override { 2239 m_delegate_sp->UpdateFieldsVisibility(); 2240 2241 window.Erase(); 2242 2243 window.DrawTitleBox(m_delegate_sp->GetName().c_str(), 2244 "Press Esc to cancel"); 2245 2246 Rect content_bounds = window.GetFrame(); 2247 content_bounds.Inset(2, 2); 2248 DerivedWindow content_surface = DerivedWindow(window, content_bounds); 2249 2250 DrawContent(content_surface); 2251 return true; 2252 } 2253 2254 void SkipNextHiddenFields() { 2255 while (true) { 2256 if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible()) 2257 return; 2258 2259 if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) { 2260 m_selection_type = SelectionType::Action; 2261 m_selection_index = 0; 2262 return; 2263 } 2264 2265 m_selection_index++; 2266 } 2267 } 2268 2269 HandleCharResult SelectNext(int key) { 2270 if (m_selection_type == SelectionType::Action) { 2271 if (m_selection_index < m_delegate_sp->GetNumberOfActions() - 1) { 2272 m_selection_index++; 2273 return eKeyHandled; 2274 } 2275 2276 m_selection_index = 0; 2277 m_selection_type = SelectionType::Field; 2278 SkipNextHiddenFields(); 2279 if (m_selection_type == SelectionType::Field) { 2280 FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index); 2281 next_field->FieldDelegateSelectFirstElement(); 2282 } 2283 return eKeyHandled; 2284 } 2285 2286 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); 2287 if (!field->FieldDelegateOnLastOrOnlyElement()) { 2288 return field->FieldDelegateHandleChar(key); 2289 } 2290 2291 field->FieldDelegateExitCallback(); 2292 2293 if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) { 2294 m_selection_type = SelectionType::Action; 2295 m_selection_index = 0; 2296 return eKeyHandled; 2297 } 2298 2299 m_selection_index++; 2300 SkipNextHiddenFields(); 2301 2302 if (m_selection_type == SelectionType::Field) { 2303 FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index); 2304 next_field->FieldDelegateSelectFirstElement(); 2305 } 2306 2307 return eKeyHandled; 2308 } 2309 2310 void SkipPreviousHiddenFields() { 2311 while (true) { 2312 if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible()) 2313 return; 2314 2315 if (m_selection_index == 0) { 2316 m_selection_type = SelectionType::Action; 2317 m_selection_index = 0; 2318 return; 2319 } 2320 2321 m_selection_index--; 2322 } 2323 } 2324 2325 HandleCharResult SelectPrevious(int key) { 2326 if (m_selection_type == SelectionType::Action) { 2327 if (m_selection_index > 0) { 2328 m_selection_index--; 2329 return eKeyHandled; 2330 } 2331 m_selection_index = m_delegate_sp->GetNumberOfFields() - 1; 2332 m_selection_type = SelectionType::Field; 2333 SkipPreviousHiddenFields(); 2334 if (m_selection_type == SelectionType::Field) { 2335 FieldDelegate *previous_field = 2336 m_delegate_sp->GetField(m_selection_index); 2337 previous_field->FieldDelegateSelectLastElement(); 2338 } 2339 return eKeyHandled; 2340 } 2341 2342 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); 2343 if (!field->FieldDelegateOnFirstOrOnlyElement()) { 2344 return field->FieldDelegateHandleChar(key); 2345 } 2346 2347 field->FieldDelegateExitCallback(); 2348 2349 if (m_selection_index == 0) { 2350 m_selection_type = SelectionType::Action; 2351 m_selection_index = m_delegate_sp->GetNumberOfActions() - 1; 2352 return eKeyHandled; 2353 } 2354 2355 m_selection_index--; 2356 SkipPreviousHiddenFields(); 2357 2358 if (m_selection_type == SelectionType::Field) { 2359 FieldDelegate *previous_field = 2360 m_delegate_sp->GetField(m_selection_index); 2361 previous_field->FieldDelegateSelectLastElement(); 2362 } 2363 2364 return eKeyHandled; 2365 } 2366 2367 void ExecuteAction(Window &window) { 2368 FormAction &action = m_delegate_sp->GetAction(m_selection_index); 2369 action.Execute(window); 2370 if (m_delegate_sp->HasError()) { 2371 m_first_visible_line = 0; 2372 m_selection_index = 0; 2373 m_selection_type = SelectionType::Field; 2374 } 2375 } 2376 2377 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { 2378 switch (key) { 2379 case '\r': 2380 case '\n': 2381 case KEY_ENTER: 2382 if (m_selection_type == SelectionType::Action) { 2383 ExecuteAction(window); 2384 return eKeyHandled; 2385 } 2386 break; 2387 case '\t': 2388 return SelectNext(key); 2389 case KEY_SHIFT_TAB: 2390 return SelectPrevious(key); 2391 case KEY_ESCAPE: 2392 window.GetParent()->RemoveSubWindow(&window); 2393 return eKeyHandled; 2394 default: 2395 break; 2396 } 2397 2398 // If the key wasn't handled and one of the fields is selected, pass the key 2399 // to that field. 2400 if (m_selection_type == SelectionType::Field) { 2401 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); 2402 return field->FieldDelegateHandleChar(key); 2403 } 2404 2405 return eKeyNotHandled; 2406 } 2407 2408 protected: 2409 FormDelegateSP m_delegate_sp; 2410 // The index of the currently selected SelectionType. 2411 int m_selection_index; 2412 // See SelectionType class enum. 2413 SelectionType m_selection_type; 2414 // The first visible line from the pad. 2415 int m_first_visible_line; 2416 }; 2417 2418 /////////////////////////// 2419 // Form Delegate Instances 2420 /////////////////////////// 2421 2422 class DetachOrKillProcessFormDelegate : public FormDelegate { 2423 public: 2424 DetachOrKillProcessFormDelegate(Process *process) : m_process(process) { 2425 SetError("There is a running process, either detach or kill it."); 2426 2427 m_keep_stopped_field = 2428 AddBooleanField("Keep process stopped when detaching.", false); 2429 2430 AddAction("Detach", [this](Window &window) { Detach(window); }); 2431 AddAction("Kill", [this](Window &window) { Kill(window); }); 2432 } 2433 2434 std::string GetName() override { return "Detach/Kill Process"; } 2435 2436 void Kill(Window &window) { 2437 Status destroy_status(m_process->Destroy(false)); 2438 if (destroy_status.Fail()) { 2439 SetError("Failed to kill process."); 2440 return; 2441 } 2442 window.GetParent()->RemoveSubWindow(&window); 2443 } 2444 2445 void Detach(Window &window) { 2446 Status detach_status(m_process->Detach(m_keep_stopped_field->GetBoolean())); 2447 if (detach_status.Fail()) { 2448 SetError("Failed to detach from process."); 2449 return; 2450 } 2451 window.GetParent()->RemoveSubWindow(&window); 2452 } 2453 2454 protected: 2455 Process *m_process; 2456 BooleanFieldDelegate *m_keep_stopped_field; 2457 }; 2458 2459 class ProcessAttachFormDelegate : public FormDelegate { 2460 public: 2461 ProcessAttachFormDelegate(Debugger &debugger, WindowSP main_window_sp) 2462 : m_debugger(debugger), m_main_window_sp(main_window_sp) { 2463 std::vector<std::string> types; 2464 types.push_back(std::string("Name")); 2465 types.push_back(std::string("PID")); 2466 m_type_field = AddChoicesField("Attach By", 2, types); 2467 m_pid_field = AddIntegerField("PID", 0, true); 2468 m_name_field = 2469 AddTextField("Process Name", GetDefaultProcessName().c_str(), true); 2470 m_continue_field = AddBooleanField("Continue once attached.", false); 2471 m_wait_for_field = AddBooleanField("Wait for process to launch.", false); 2472 m_include_existing_field = 2473 AddBooleanField("Include existing processes.", false); 2474 m_show_advanced_field = AddBooleanField("Show advanced settings.", false); 2475 m_plugin_field = AddProcessPluginField(); 2476 2477 AddAction("Attach", [this](Window &window) { Attach(window); }); 2478 } 2479 2480 std::string GetName() override { return "Attach Process"; } 2481 2482 void UpdateFieldsVisibility() override { 2483 if (m_type_field->GetChoiceContent() == "Name") { 2484 m_pid_field->FieldDelegateHide(); 2485 m_name_field->FieldDelegateShow(); 2486 m_wait_for_field->FieldDelegateShow(); 2487 if (m_wait_for_field->GetBoolean()) 2488 m_include_existing_field->FieldDelegateShow(); 2489 else 2490 m_include_existing_field->FieldDelegateHide(); 2491 } else { 2492 m_pid_field->FieldDelegateShow(); 2493 m_name_field->FieldDelegateHide(); 2494 m_wait_for_field->FieldDelegateHide(); 2495 m_include_existing_field->FieldDelegateHide(); 2496 } 2497 if (m_show_advanced_field->GetBoolean()) 2498 m_plugin_field->FieldDelegateShow(); 2499 else 2500 m_plugin_field->FieldDelegateHide(); 2501 } 2502 2503 // Get the basename of the target's main executable if available, empty string 2504 // otherwise. 2505 std::string GetDefaultProcessName() { 2506 Target *target = m_debugger.GetSelectedTarget().get(); 2507 if (target == nullptr) 2508 return ""; 2509 2510 ModuleSP module_sp = target->GetExecutableModule(); 2511 if (!module_sp->IsExecutable()) 2512 return ""; 2513 2514 return module_sp->GetFileSpec().GetFilename().AsCString(); 2515 } 2516 2517 bool StopRunningProcess() { 2518 ExecutionContext exe_ctx = 2519 m_debugger.GetCommandInterpreter().GetExecutionContext(); 2520 2521 if (!exe_ctx.HasProcessScope()) 2522 return false; 2523 2524 Process *process = exe_ctx.GetProcessPtr(); 2525 if (!(process && process->IsAlive())) 2526 return false; 2527 2528 FormDelegateSP form_delegate_sp = 2529 FormDelegateSP(new DetachOrKillProcessFormDelegate(process)); 2530 Rect bounds = m_main_window_sp->GetCenteredRect(85, 8); 2531 WindowSP form_window_sp = m_main_window_sp->CreateSubWindow( 2532 form_delegate_sp->GetName().c_str(), bounds, true); 2533 WindowDelegateSP window_delegate_sp = 2534 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); 2535 form_window_sp->SetDelegate(window_delegate_sp); 2536 2537 return true; 2538 } 2539 2540 Target *GetTarget() { 2541 Target *target = m_debugger.GetSelectedTarget().get(); 2542 2543 if (target != nullptr) 2544 return target; 2545 2546 TargetSP new_target_sp; 2547 m_debugger.GetTargetList().CreateTarget( 2548 m_debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp); 2549 2550 target = new_target_sp.get(); 2551 2552 if (target == nullptr) 2553 SetError("Failed to create target."); 2554 2555 m_debugger.GetTargetList().SetSelectedTarget(new_target_sp); 2556 2557 return target; 2558 } 2559 2560 ProcessAttachInfo GetAttachInfo() { 2561 ProcessAttachInfo attach_info; 2562 attach_info.SetContinueOnceAttached(m_continue_field->GetBoolean()); 2563 if (m_type_field->GetChoiceContent() == "Name") { 2564 attach_info.GetExecutableFile().SetFile(m_name_field->GetText(), 2565 FileSpec::Style::native); 2566 attach_info.SetWaitForLaunch(m_wait_for_field->GetBoolean()); 2567 if (m_wait_for_field->GetBoolean()) 2568 attach_info.SetIgnoreExisting(!m_include_existing_field->GetBoolean()); 2569 } else { 2570 attach_info.SetProcessID(m_pid_field->GetInteger()); 2571 } 2572 attach_info.SetProcessPluginName(m_plugin_field->GetPluginName()); 2573 2574 return attach_info; 2575 } 2576 2577 void Attach(Window &window) { 2578 ClearError(); 2579 2580 bool all_fields_are_valid = CheckFieldsValidity(); 2581 if (!all_fields_are_valid) 2582 return; 2583 2584 bool process_is_running = StopRunningProcess(); 2585 if (process_is_running) 2586 return; 2587 2588 Target *target = GetTarget(); 2589 if (HasError()) 2590 return; 2591 2592 StreamString stream; 2593 ProcessAttachInfo attach_info = GetAttachInfo(); 2594 Status status = target->Attach(attach_info, &stream); 2595 2596 if (status.Fail()) { 2597 SetError(status.AsCString()); 2598 return; 2599 } 2600 2601 ProcessSP process_sp(target->GetProcessSP()); 2602 if (!process_sp) { 2603 SetError("Attached sucessfully but target has no process."); 2604 return; 2605 } 2606 2607 if (attach_info.GetContinueOnceAttached()) 2608 process_sp->Resume(); 2609 2610 window.GetParent()->RemoveSubWindow(&window); 2611 } 2612 2613 protected: 2614 Debugger &m_debugger; 2615 WindowSP m_main_window_sp; 2616 2617 ChoicesFieldDelegate *m_type_field; 2618 IntegerFieldDelegate *m_pid_field; 2619 TextFieldDelegate *m_name_field; 2620 BooleanFieldDelegate *m_continue_field; 2621 BooleanFieldDelegate *m_wait_for_field; 2622 BooleanFieldDelegate *m_include_existing_field; 2623 BooleanFieldDelegate *m_show_advanced_field; 2624 ProcessPluginFieldDelegate *m_plugin_field; 2625 }; 2626 2627 class MenuDelegate { 2628 public: 2629 virtual ~MenuDelegate() = default; 2630 2631 virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0; 2632 }; 2633 2634 class Menu : public WindowDelegate { 2635 public: 2636 enum class Type { Invalid, Bar, Item, Separator }; 2637 2638 // Menubar or separator constructor 2639 Menu(Type type); 2640 2641 // Menuitem constructor 2642 Menu(const char *name, const char *key_name, int key_value, 2643 uint64_t identifier); 2644 2645 ~Menu() override = default; 2646 2647 const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; } 2648 2649 void SetDelegate(const MenuDelegateSP &delegate_sp) { 2650 m_delegate_sp = delegate_sp; 2651 } 2652 2653 void RecalculateNameLengths(); 2654 2655 void AddSubmenu(const MenuSP &menu_sp); 2656 2657 int DrawAndRunMenu(Window &window); 2658 2659 void DrawMenuTitle(Window &window, bool highlight); 2660 2661 bool WindowDelegateDraw(Window &window, bool force) override; 2662 2663 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override; 2664 2665 MenuActionResult ActionPrivate(Menu &menu) { 2666 MenuActionResult result = MenuActionResult::NotHandled; 2667 if (m_delegate_sp) { 2668 result = m_delegate_sp->MenuDelegateAction(menu); 2669 if (result != MenuActionResult::NotHandled) 2670 return result; 2671 } else if (m_parent) { 2672 result = m_parent->ActionPrivate(menu); 2673 if (result != MenuActionResult::NotHandled) 2674 return result; 2675 } 2676 return m_canned_result; 2677 } 2678 2679 MenuActionResult Action() { 2680 // Call the recursive action so it can try to handle it with the menu 2681 // delegate, and if not, try our parent menu 2682 return ActionPrivate(*this); 2683 } 2684 2685 void SetCannedResult(MenuActionResult result) { m_canned_result = result; } 2686 2687 Menus &GetSubmenus() { return m_submenus; } 2688 2689 const Menus &GetSubmenus() const { return m_submenus; } 2690 2691 int GetSelectedSubmenuIndex() const { return m_selected; } 2692 2693 void SetSelectedSubmenuIndex(int idx) { m_selected = idx; } 2694 2695 Type GetType() const { return m_type; } 2696 2697 int GetStartingColumn() const { return m_start_col; } 2698 2699 void SetStartingColumn(int col) { m_start_col = col; } 2700 2701 int GetKeyValue() const { return m_key_value; } 2702 2703 std::string &GetName() { return m_name; } 2704 2705 int GetDrawWidth() const { 2706 return m_max_submenu_name_length + m_max_submenu_key_name_length + 8; 2707 } 2708 2709 uint64_t GetIdentifier() const { return m_identifier; } 2710 2711 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } 2712 2713 protected: 2714 std::string m_name; 2715 std::string m_key_name; 2716 uint64_t m_identifier; 2717 Type m_type; 2718 int m_key_value; 2719 int m_start_col; 2720 int m_max_submenu_name_length; 2721 int m_max_submenu_key_name_length; 2722 int m_selected; 2723 Menu *m_parent; 2724 Menus m_submenus; 2725 WindowSP m_menu_window_sp; 2726 MenuActionResult m_canned_result; 2727 MenuDelegateSP m_delegate_sp; 2728 }; 2729 2730 // Menubar or separator constructor 2731 Menu::Menu(Type type) 2732 : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0), 2733 m_start_col(0), m_max_submenu_name_length(0), 2734 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr), 2735 m_submenus(), m_canned_result(MenuActionResult::NotHandled), 2736 m_delegate_sp() {} 2737 2738 // Menuitem constructor 2739 Menu::Menu(const char *name, const char *key_name, int key_value, 2740 uint64_t identifier) 2741 : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid), 2742 m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0), 2743 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr), 2744 m_submenus(), m_canned_result(MenuActionResult::NotHandled), 2745 m_delegate_sp() { 2746 if (name && name[0]) { 2747 m_name = name; 2748 m_type = Type::Item; 2749 if (key_name && key_name[0]) 2750 m_key_name = key_name; 2751 } else { 2752 m_type = Type::Separator; 2753 } 2754 } 2755 2756 void Menu::RecalculateNameLengths() { 2757 m_max_submenu_name_length = 0; 2758 m_max_submenu_key_name_length = 0; 2759 Menus &submenus = GetSubmenus(); 2760 const size_t num_submenus = submenus.size(); 2761 for (size_t i = 0; i < num_submenus; ++i) { 2762 Menu *submenu = submenus[i].get(); 2763 if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size()) 2764 m_max_submenu_name_length = submenu->m_name.size(); 2765 if (static_cast<size_t>(m_max_submenu_key_name_length) < 2766 submenu->m_key_name.size()) 2767 m_max_submenu_key_name_length = submenu->m_key_name.size(); 2768 } 2769 } 2770 2771 void Menu::AddSubmenu(const MenuSP &menu_sp) { 2772 menu_sp->m_parent = this; 2773 if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size()) 2774 m_max_submenu_name_length = menu_sp->m_name.size(); 2775 if (static_cast<size_t>(m_max_submenu_key_name_length) < 2776 menu_sp->m_key_name.size()) 2777 m_max_submenu_key_name_length = menu_sp->m_key_name.size(); 2778 m_submenus.push_back(menu_sp); 2779 } 2780 2781 void Menu::DrawMenuTitle(Window &window, bool highlight) { 2782 if (m_type == Type::Separator) { 2783 window.MoveCursor(0, window.GetCursorY()); 2784 window.PutChar(ACS_LTEE); 2785 int width = window.GetWidth(); 2786 if (width > 2) { 2787 width -= 2; 2788 for (int i = 0; i < width; ++i) 2789 window.PutChar(ACS_HLINE); 2790 } 2791 window.PutChar(ACS_RTEE); 2792 } else { 2793 const int shortcut_key = m_key_value; 2794 bool underlined_shortcut = false; 2795 const attr_t highlight_attr = A_REVERSE; 2796 if (highlight) 2797 window.AttributeOn(highlight_attr); 2798 if (llvm::isPrint(shortcut_key)) { 2799 size_t lower_pos = m_name.find(tolower(shortcut_key)); 2800 size_t upper_pos = m_name.find(toupper(shortcut_key)); 2801 const char *name = m_name.c_str(); 2802 size_t pos = std::min<size_t>(lower_pos, upper_pos); 2803 if (pos != std::string::npos) { 2804 underlined_shortcut = true; 2805 if (pos > 0) { 2806 window.PutCString(name, pos); 2807 name += pos; 2808 } 2809 const attr_t shortcut_attr = A_UNDERLINE | A_BOLD; 2810 window.AttributeOn(shortcut_attr); 2811 window.PutChar(name[0]); 2812 window.AttributeOff(shortcut_attr); 2813 name++; 2814 if (name[0]) 2815 window.PutCString(name); 2816 } 2817 } 2818 2819 if (!underlined_shortcut) { 2820 window.PutCString(m_name.c_str()); 2821 } 2822 2823 if (highlight) 2824 window.AttributeOff(highlight_attr); 2825 2826 if (m_key_name.empty()) { 2827 if (!underlined_shortcut && llvm::isPrint(m_key_value)) { 2828 window.AttributeOn(COLOR_PAIR(MagentaOnWhite)); 2829 window.Printf(" (%c)", m_key_value); 2830 window.AttributeOff(COLOR_PAIR(MagentaOnWhite)); 2831 } 2832 } else { 2833 window.AttributeOn(COLOR_PAIR(MagentaOnWhite)); 2834 window.Printf(" (%s)", m_key_name.c_str()); 2835 window.AttributeOff(COLOR_PAIR(MagentaOnWhite)); 2836 } 2837 } 2838 } 2839 2840 bool Menu::WindowDelegateDraw(Window &window, bool force) { 2841 Menus &submenus = GetSubmenus(); 2842 const size_t num_submenus = submenus.size(); 2843 const int selected_idx = GetSelectedSubmenuIndex(); 2844 Menu::Type menu_type = GetType(); 2845 switch (menu_type) { 2846 case Menu::Type::Bar: { 2847 window.SetBackground(BlackOnWhite); 2848 window.MoveCursor(0, 0); 2849 for (size_t i = 0; i < num_submenus; ++i) { 2850 Menu *menu = submenus[i].get(); 2851 if (i > 0) 2852 window.PutChar(' '); 2853 menu->SetStartingColumn(window.GetCursorX()); 2854 window.PutCString("| "); 2855 menu->DrawMenuTitle(window, false); 2856 } 2857 window.PutCString(" |"); 2858 } break; 2859 2860 case Menu::Type::Item: { 2861 int y = 1; 2862 int x = 3; 2863 // Draw the menu 2864 int cursor_x = 0; 2865 int cursor_y = 0; 2866 window.Erase(); 2867 window.SetBackground(BlackOnWhite); 2868 window.Box(); 2869 for (size_t i = 0; i < num_submenus; ++i) { 2870 const bool is_selected = (i == static_cast<size_t>(selected_idx)); 2871 window.MoveCursor(x, y + i); 2872 if (is_selected) { 2873 // Remember where we want the cursor to be 2874 cursor_x = x - 1; 2875 cursor_y = y + i; 2876 } 2877 submenus[i]->DrawMenuTitle(window, is_selected); 2878 } 2879 window.MoveCursor(cursor_x, cursor_y); 2880 } break; 2881 2882 default: 2883 case Menu::Type::Separator: 2884 break; 2885 } 2886 return true; // Drawing handled... 2887 } 2888 2889 HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) { 2890 HandleCharResult result = eKeyNotHandled; 2891 2892 Menus &submenus = GetSubmenus(); 2893 const size_t num_submenus = submenus.size(); 2894 const int selected_idx = GetSelectedSubmenuIndex(); 2895 Menu::Type menu_type = GetType(); 2896 if (menu_type == Menu::Type::Bar) { 2897 MenuSP run_menu_sp; 2898 switch (key) { 2899 case KEY_DOWN: 2900 case KEY_UP: 2901 // Show last menu or first menu 2902 if (selected_idx < static_cast<int>(num_submenus)) 2903 run_menu_sp = submenus[selected_idx]; 2904 else if (!submenus.empty()) 2905 run_menu_sp = submenus.front(); 2906 result = eKeyHandled; 2907 break; 2908 2909 case KEY_RIGHT: 2910 ++m_selected; 2911 if (m_selected >= static_cast<int>(num_submenus)) 2912 m_selected = 0; 2913 if (m_selected < static_cast<int>(num_submenus)) 2914 run_menu_sp = submenus[m_selected]; 2915 else if (!submenus.empty()) 2916 run_menu_sp = submenus.front(); 2917 result = eKeyHandled; 2918 break; 2919 2920 case KEY_LEFT: 2921 --m_selected; 2922 if (m_selected < 0) 2923 m_selected = num_submenus - 1; 2924 if (m_selected < static_cast<int>(num_submenus)) 2925 run_menu_sp = submenus[m_selected]; 2926 else if (!submenus.empty()) 2927 run_menu_sp = submenus.front(); 2928 result = eKeyHandled; 2929 break; 2930 2931 default: 2932 for (size_t i = 0; i < num_submenus; ++i) { 2933 if (submenus[i]->GetKeyValue() == key) { 2934 SetSelectedSubmenuIndex(i); 2935 run_menu_sp = submenus[i]; 2936 result = eKeyHandled; 2937 break; 2938 } 2939 } 2940 break; 2941 } 2942 2943 if (run_menu_sp) { 2944 // Run the action on this menu in case we need to populate the menu with 2945 // dynamic content and also in case check marks, and any other menu 2946 // decorations need to be calculated 2947 if (run_menu_sp->Action() == MenuActionResult::Quit) 2948 return eQuitApplication; 2949 2950 Rect menu_bounds; 2951 menu_bounds.origin.x = run_menu_sp->GetStartingColumn(); 2952 menu_bounds.origin.y = 1; 2953 menu_bounds.size.width = run_menu_sp->GetDrawWidth(); 2954 menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2; 2955 if (m_menu_window_sp) 2956 window.GetParent()->RemoveSubWindow(m_menu_window_sp.get()); 2957 2958 m_menu_window_sp = window.GetParent()->CreateSubWindow( 2959 run_menu_sp->GetName().c_str(), menu_bounds, true); 2960 m_menu_window_sp->SetDelegate(run_menu_sp); 2961 } 2962 } else if (menu_type == Menu::Type::Item) { 2963 switch (key) { 2964 case KEY_DOWN: 2965 if (m_submenus.size() > 1) { 2966 const int start_select = m_selected; 2967 while (++m_selected != start_select) { 2968 if (static_cast<size_t>(m_selected) >= num_submenus) 2969 m_selected = 0; 2970 if (m_submenus[m_selected]->GetType() == Type::Separator) 2971 continue; 2972 else 2973 break; 2974 } 2975 return eKeyHandled; 2976 } 2977 break; 2978 2979 case KEY_UP: 2980 if (m_submenus.size() > 1) { 2981 const int start_select = m_selected; 2982 while (--m_selected != start_select) { 2983 if (m_selected < static_cast<int>(0)) 2984 m_selected = num_submenus - 1; 2985 if (m_submenus[m_selected]->GetType() == Type::Separator) 2986 continue; 2987 else 2988 break; 2989 } 2990 return eKeyHandled; 2991 } 2992 break; 2993 2994 case KEY_RETURN: 2995 if (static_cast<size_t>(selected_idx) < num_submenus) { 2996 if (submenus[selected_idx]->Action() == MenuActionResult::Quit) 2997 return eQuitApplication; 2998 window.GetParent()->RemoveSubWindow(&window); 2999 return eKeyHandled; 3000 } 3001 break; 3002 3003 case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in 3004 // case other chars are entered for escaped sequences 3005 window.GetParent()->RemoveSubWindow(&window); 3006 return eKeyHandled; 3007 3008 default: 3009 for (size_t i = 0; i < num_submenus; ++i) { 3010 Menu *menu = submenus[i].get(); 3011 if (menu->GetKeyValue() == key) { 3012 SetSelectedSubmenuIndex(i); 3013 window.GetParent()->RemoveSubWindow(&window); 3014 if (menu->Action() == MenuActionResult::Quit) 3015 return eQuitApplication; 3016 return eKeyHandled; 3017 } 3018 } 3019 break; 3020 } 3021 } else if (menu_type == Menu::Type::Separator) { 3022 } 3023 return result; 3024 } 3025 3026 class Application { 3027 public: 3028 Application(FILE *in, FILE *out) 3029 : m_window_sp(), m_screen(nullptr), m_in(in), m_out(out) {} 3030 3031 ~Application() { 3032 m_window_delegates.clear(); 3033 m_window_sp.reset(); 3034 if (m_screen) { 3035 ::delscreen(m_screen); 3036 m_screen = nullptr; 3037 } 3038 } 3039 3040 void Initialize() { 3041 m_screen = ::newterm(nullptr, m_out, m_in); 3042 ::start_color(); 3043 ::curs_set(0); 3044 ::noecho(); 3045 ::keypad(stdscr, TRUE); 3046 } 3047 3048 void Terminate() { ::endwin(); } 3049 3050 void Run(Debugger &debugger) { 3051 bool done = false; 3052 int delay_in_tenths_of_a_second = 1; 3053 3054 // Alas the threading model in curses is a bit lame so we need to resort to 3055 // polling every 0.5 seconds. We could poll for stdin ourselves and then 3056 // pass the keys down but then we need to translate all of the escape 3057 // sequences ourselves. So we resort to polling for input because we need 3058 // to receive async process events while in this loop. 3059 3060 halfdelay(delay_in_tenths_of_a_second); // Poll using some number of tenths 3061 // of seconds seconds when calling 3062 // Window::GetChar() 3063 3064 ListenerSP listener_sp( 3065 Listener::MakeListener("lldb.IOHandler.curses.Application")); 3066 ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass()); 3067 debugger.EnableForwardEvents(listener_sp); 3068 3069 m_update_screen = true; 3070 #if defined(__APPLE__) 3071 std::deque<int> escape_chars; 3072 #endif 3073 3074 while (!done) { 3075 if (m_update_screen) { 3076 m_window_sp->Draw(false); 3077 // All windows should be calling Window::DeferredRefresh() instead of 3078 // Window::Refresh() so we can do a single update and avoid any screen 3079 // blinking 3080 update_panels(); 3081 3082 // Cursor hiding isn't working on MacOSX, so hide it in the top left 3083 // corner 3084 m_window_sp->MoveCursor(0, 0); 3085 3086 doupdate(); 3087 m_update_screen = false; 3088 } 3089 3090 #if defined(__APPLE__) 3091 // Terminal.app doesn't map its function keys correctly, F1-F4 default 3092 // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if 3093 // possible 3094 int ch; 3095 if (escape_chars.empty()) 3096 ch = m_window_sp->GetChar(); 3097 else { 3098 ch = escape_chars.front(); 3099 escape_chars.pop_front(); 3100 } 3101 if (ch == KEY_ESCAPE) { 3102 int ch2 = m_window_sp->GetChar(); 3103 if (ch2 == 'O') { 3104 int ch3 = m_window_sp->GetChar(); 3105 switch (ch3) { 3106 case 'P': 3107 ch = KEY_F(1); 3108 break; 3109 case 'Q': 3110 ch = KEY_F(2); 3111 break; 3112 case 'R': 3113 ch = KEY_F(3); 3114 break; 3115 case 'S': 3116 ch = KEY_F(4); 3117 break; 3118 default: 3119 escape_chars.push_back(ch2); 3120 if (ch3 != -1) 3121 escape_chars.push_back(ch3); 3122 break; 3123 } 3124 } else if (ch2 != -1) 3125 escape_chars.push_back(ch2); 3126 } 3127 #else 3128 int ch = m_window_sp->GetChar(); 3129 3130 #endif 3131 if (ch == -1) { 3132 if (feof(m_in) || ferror(m_in)) { 3133 done = true; 3134 } else { 3135 // Just a timeout from using halfdelay(), check for events 3136 EventSP event_sp; 3137 while (listener_sp->PeekAtNextEvent()) { 3138 listener_sp->GetEvent(event_sp, std::chrono::seconds(0)); 3139 3140 if (event_sp) { 3141 Broadcaster *broadcaster = event_sp->GetBroadcaster(); 3142 if (broadcaster) { 3143 // uint32_t event_type = event_sp->GetType(); 3144 ConstString broadcaster_class( 3145 broadcaster->GetBroadcasterClass()); 3146 if (broadcaster_class == broadcaster_class_process) { 3147 m_update_screen = true; 3148 continue; // Don't get any key, just update our view 3149 } 3150 } 3151 } 3152 } 3153 } 3154 } else { 3155 HandleCharResult key_result = m_window_sp->HandleChar(ch); 3156 switch (key_result) { 3157 case eKeyHandled: 3158 m_update_screen = true; 3159 break; 3160 case eKeyNotHandled: 3161 if (ch == 12) { // Ctrl+L, force full redraw 3162 redrawwin(m_window_sp->get()); 3163 m_update_screen = true; 3164 } 3165 break; 3166 case eQuitApplication: 3167 done = true; 3168 break; 3169 } 3170 } 3171 } 3172 3173 debugger.CancelForwardEvents(listener_sp); 3174 } 3175 3176 WindowSP &GetMainWindow() { 3177 if (!m_window_sp) 3178 m_window_sp = std::make_shared<Window>("main", stdscr, false); 3179 return m_window_sp; 3180 } 3181 3182 void TerminalSizeChanged() { 3183 ::endwin(); 3184 ::refresh(); 3185 Rect content_bounds = m_window_sp->GetFrame(); 3186 m_window_sp->SetBounds(content_bounds); 3187 if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar")) 3188 menubar_window_sp->SetBounds(content_bounds.MakeMenuBar()); 3189 if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status")) 3190 status_window_sp->SetBounds(content_bounds.MakeStatusBar()); 3191 3192 WindowSP source_window_sp = m_window_sp->FindSubWindow("Source"); 3193 WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables"); 3194 WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers"); 3195 WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads"); 3196 3197 Rect threads_bounds; 3198 Rect source_variables_bounds; 3199 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, 3200 threads_bounds); 3201 if (threads_window_sp) 3202 threads_window_sp->SetBounds(threads_bounds); 3203 else 3204 source_variables_bounds = content_bounds; 3205 3206 Rect source_bounds; 3207 Rect variables_registers_bounds; 3208 source_variables_bounds.HorizontalSplitPercentage( 3209 0.70, source_bounds, variables_registers_bounds); 3210 if (variables_window_sp || registers_window_sp) { 3211 if (variables_window_sp && registers_window_sp) { 3212 Rect variables_bounds; 3213 Rect registers_bounds; 3214 variables_registers_bounds.VerticalSplitPercentage( 3215 0.50, variables_bounds, registers_bounds); 3216 variables_window_sp->SetBounds(variables_bounds); 3217 registers_window_sp->SetBounds(registers_bounds); 3218 } else if (variables_window_sp) { 3219 variables_window_sp->SetBounds(variables_registers_bounds); 3220 } else { 3221 registers_window_sp->SetBounds(variables_registers_bounds); 3222 } 3223 } else { 3224 source_bounds = source_variables_bounds; 3225 } 3226 3227 source_window_sp->SetBounds(source_bounds); 3228 3229 touchwin(stdscr); 3230 redrawwin(m_window_sp->get()); 3231 m_update_screen = true; 3232 } 3233 3234 protected: 3235 WindowSP m_window_sp; 3236 WindowDelegates m_window_delegates; 3237 SCREEN *m_screen; 3238 FILE *m_in; 3239 FILE *m_out; 3240 bool m_update_screen = false; 3241 }; 3242 3243 } // namespace curses 3244 3245 using namespace curses; 3246 3247 struct Row { 3248 ValueObjectUpdater value; 3249 Row *parent; 3250 // The process stop ID when the children were calculated. 3251 uint32_t children_stop_id = 0; 3252 int row_idx = 0; 3253 int x = 1; 3254 int y = 1; 3255 bool might_have_children; 3256 bool expanded = false; 3257 bool calculated_children = false; 3258 std::vector<Row> children; 3259 3260 Row(const ValueObjectSP &v, Row *p) 3261 : value(v), parent(p), 3262 might_have_children(v ? v->MightHaveChildren() : false) {} 3263 3264 size_t GetDepth() const { 3265 if (parent) 3266 return 1 + parent->GetDepth(); 3267 return 0; 3268 } 3269 3270 void Expand() { expanded = true; } 3271 3272 std::vector<Row> &GetChildren() { 3273 ProcessSP process_sp = value.GetProcessSP(); 3274 auto stop_id = process_sp->GetStopID(); 3275 if (process_sp && stop_id != children_stop_id) { 3276 children_stop_id = stop_id; 3277 calculated_children = false; 3278 } 3279 if (!calculated_children) { 3280 children.clear(); 3281 calculated_children = true; 3282 ValueObjectSP valobj = value.GetSP(); 3283 if (valobj) { 3284 const size_t num_children = valobj->GetNumChildren(); 3285 for (size_t i = 0; i < num_children; ++i) { 3286 children.push_back(Row(valobj->GetChildAtIndex(i, true), this)); 3287 } 3288 } 3289 } 3290 return children; 3291 } 3292 3293 void Unexpand() { 3294 expanded = false; 3295 calculated_children = false; 3296 children.clear(); 3297 } 3298 3299 void DrawTree(Window &window) { 3300 if (parent) 3301 parent->DrawTreeForChild(window, this, 0); 3302 3303 if (might_have_children) { 3304 // It we can get UTF8 characters to work we should try to use the 3305 // "symbol" UTF8 string below 3306 // const char *symbol = ""; 3307 // if (row.expanded) 3308 // symbol = "\xe2\x96\xbd "; 3309 // else 3310 // symbol = "\xe2\x96\xb7 "; 3311 // window.PutCString (symbol); 3312 3313 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v' 3314 // or '>' character... 3315 // if (expanded) 3316 // window.PutChar (ACS_DARROW); 3317 // else 3318 // window.PutChar (ACS_RARROW); 3319 // Since we can't find any good looking right arrow/down arrow symbols, 3320 // just use a diamond... 3321 window.PutChar(ACS_DIAMOND); 3322 window.PutChar(ACS_HLINE); 3323 } 3324 } 3325 3326 void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) { 3327 if (parent) 3328 parent->DrawTreeForChild(window, this, reverse_depth + 1); 3329 3330 if (&GetChildren().back() == child) { 3331 // Last child 3332 if (reverse_depth == 0) { 3333 window.PutChar(ACS_LLCORNER); 3334 window.PutChar(ACS_HLINE); 3335 } else { 3336 window.PutChar(' '); 3337 window.PutChar(' '); 3338 } 3339 } else { 3340 if (reverse_depth == 0) { 3341 window.PutChar(ACS_LTEE); 3342 window.PutChar(ACS_HLINE); 3343 } else { 3344 window.PutChar(ACS_VLINE); 3345 window.PutChar(' '); 3346 } 3347 } 3348 } 3349 }; 3350 3351 struct DisplayOptions { 3352 bool show_types; 3353 }; 3354 3355 class TreeItem; 3356 3357 class TreeDelegate { 3358 public: 3359 TreeDelegate() = default; 3360 virtual ~TreeDelegate() = default; 3361 3362 virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0; 3363 virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0; 3364 virtual void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index, 3365 TreeItem *&selected_item) { 3366 return; 3367 } 3368 virtual bool TreeDelegateItemSelected( 3369 TreeItem &item) = 0; // Return true if we need to update views 3370 virtual bool TreeDelegateExpandRootByDefault() { return false; } 3371 }; 3372 3373 typedef std::shared_ptr<TreeDelegate> TreeDelegateSP; 3374 3375 class TreeItem { 3376 public: 3377 TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children) 3378 : m_parent(parent), m_delegate(delegate), m_user_data(nullptr), 3379 m_identifier(0), m_row_idx(-1), m_children(), 3380 m_might_have_children(might_have_children), m_is_expanded(false) { 3381 if (m_parent == nullptr) 3382 m_is_expanded = m_delegate.TreeDelegateExpandRootByDefault(); 3383 } 3384 3385 TreeItem &operator=(const TreeItem &rhs) { 3386 if (this != &rhs) { 3387 m_parent = rhs.m_parent; 3388 m_delegate = rhs.m_delegate; 3389 m_user_data = rhs.m_user_data; 3390 m_identifier = rhs.m_identifier; 3391 m_row_idx = rhs.m_row_idx; 3392 m_children = rhs.m_children; 3393 m_might_have_children = rhs.m_might_have_children; 3394 m_is_expanded = rhs.m_is_expanded; 3395 } 3396 return *this; 3397 } 3398 3399 TreeItem(const TreeItem &) = default; 3400 3401 size_t GetDepth() const { 3402 if (m_parent) 3403 return 1 + m_parent->GetDepth(); 3404 return 0; 3405 } 3406 3407 int GetRowIndex() const { return m_row_idx; } 3408 3409 void ClearChildren() { m_children.clear(); } 3410 3411 void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); } 3412 3413 TreeItem &operator[](size_t i) { return m_children[i]; } 3414 3415 void SetRowIndex(int row_idx) { m_row_idx = row_idx; } 3416 3417 size_t GetNumChildren() { 3418 m_delegate.TreeDelegateGenerateChildren(*this); 3419 return m_children.size(); 3420 } 3421 3422 void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); } 3423 3424 void CalculateRowIndexes(int &row_idx) { 3425 SetRowIndex(row_idx); 3426 ++row_idx; 3427 3428 const bool expanded = IsExpanded(); 3429 3430 // The root item must calculate its children, or we must calculate the 3431 // number of children if the item is expanded 3432 if (m_parent == nullptr || expanded) 3433 GetNumChildren(); 3434 3435 for (auto &item : m_children) { 3436 if (expanded) 3437 item.CalculateRowIndexes(row_idx); 3438 else 3439 item.SetRowIndex(-1); 3440 } 3441 } 3442 3443 TreeItem *GetParent() { return m_parent; } 3444 3445 bool IsExpanded() const { return m_is_expanded; } 3446 3447 void Expand() { m_is_expanded = true; } 3448 3449 void Unexpand() { m_is_expanded = false; } 3450 3451 bool Draw(Window &window, const int first_visible_row, 3452 const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) { 3453 if (num_rows_left <= 0) 3454 return false; 3455 3456 if (m_row_idx >= first_visible_row) { 3457 window.MoveCursor(2, row_idx + 1); 3458 3459 if (m_parent) 3460 m_parent->DrawTreeForChild(window, this, 0); 3461 3462 if (m_might_have_children) { 3463 // It we can get UTF8 characters to work we should try to use the 3464 // "symbol" UTF8 string below 3465 // const char *symbol = ""; 3466 // if (row.expanded) 3467 // symbol = "\xe2\x96\xbd "; 3468 // else 3469 // symbol = "\xe2\x96\xb7 "; 3470 // window.PutCString (symbol); 3471 3472 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 3473 // 'v' or '>' character... 3474 // if (expanded) 3475 // window.PutChar (ACS_DARROW); 3476 // else 3477 // window.PutChar (ACS_RARROW); 3478 // Since we can't find any good looking right arrow/down arrow symbols, 3479 // just use a diamond... 3480 window.PutChar(ACS_DIAMOND); 3481 window.PutChar(ACS_HLINE); 3482 } 3483 bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) && 3484 window.IsActive(); 3485 3486 if (highlight) 3487 window.AttributeOn(A_REVERSE); 3488 3489 m_delegate.TreeDelegateDrawTreeItem(*this, window); 3490 3491 if (highlight) 3492 window.AttributeOff(A_REVERSE); 3493 ++row_idx; 3494 --num_rows_left; 3495 } 3496 3497 if (num_rows_left <= 0) 3498 return false; // We are done drawing... 3499 3500 if (IsExpanded()) { 3501 for (auto &item : m_children) { 3502 // If we displayed all the rows and item.Draw() returns false we are 3503 // done drawing and can exit this for loop 3504 if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx, 3505 num_rows_left)) 3506 break; 3507 } 3508 } 3509 return num_rows_left >= 0; // Return true if not done drawing yet 3510 } 3511 3512 void DrawTreeForChild(Window &window, TreeItem *child, 3513 uint32_t reverse_depth) { 3514 if (m_parent) 3515 m_parent->DrawTreeForChild(window, this, reverse_depth + 1); 3516 3517 if (&m_children.back() == child) { 3518 // Last child 3519 if (reverse_depth == 0) { 3520 window.PutChar(ACS_LLCORNER); 3521 window.PutChar(ACS_HLINE); 3522 } else { 3523 window.PutChar(' '); 3524 window.PutChar(' '); 3525 } 3526 } else { 3527 if (reverse_depth == 0) { 3528 window.PutChar(ACS_LTEE); 3529 window.PutChar(ACS_HLINE); 3530 } else { 3531 window.PutChar(ACS_VLINE); 3532 window.PutChar(' '); 3533 } 3534 } 3535 } 3536 3537 TreeItem *GetItemForRowIndex(uint32_t row_idx) { 3538 if (static_cast<uint32_t>(m_row_idx) == row_idx) 3539 return this; 3540 if (m_children.empty()) 3541 return nullptr; 3542 if (IsExpanded()) { 3543 for (auto &item : m_children) { 3544 TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx); 3545 if (selected_item_ptr) 3546 return selected_item_ptr; 3547 } 3548 } 3549 return nullptr; 3550 } 3551 3552 void *GetUserData() const { return m_user_data; } 3553 3554 void SetUserData(void *user_data) { m_user_data = user_data; } 3555 3556 uint64_t GetIdentifier() const { return m_identifier; } 3557 3558 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } 3559 3560 void SetMightHaveChildren(bool b) { m_might_have_children = b; } 3561 3562 protected: 3563 TreeItem *m_parent; 3564 TreeDelegate &m_delegate; 3565 void *m_user_data; 3566 uint64_t m_identifier; 3567 int m_row_idx; // Zero based visible row index, -1 if not visible or for the 3568 // root item 3569 std::vector<TreeItem> m_children; 3570 bool m_might_have_children; 3571 bool m_is_expanded; 3572 }; 3573 3574 class TreeWindowDelegate : public WindowDelegate { 3575 public: 3576 TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp) 3577 : m_debugger(debugger), m_delegate_sp(delegate_sp), 3578 m_root(nullptr, *delegate_sp, true), m_selected_item(nullptr), 3579 m_num_rows(0), m_selected_row_idx(0), m_first_visible_row(0), 3580 m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {} 3581 3582 int NumVisibleRows() const { return m_max_y - m_min_y; } 3583 3584 bool WindowDelegateDraw(Window &window, bool force) override { 3585 ExecutionContext exe_ctx( 3586 m_debugger.GetCommandInterpreter().GetExecutionContext()); 3587 Process *process = exe_ctx.GetProcessPtr(); 3588 3589 bool display_content = false; 3590 if (process) { 3591 StateType state = process->GetState(); 3592 if (StateIsStoppedState(state, true)) { 3593 // We are stopped, so it is ok to 3594 display_content = true; 3595 } else if (StateIsRunningState(state)) { 3596 return true; // Don't do any updating when we are running 3597 } 3598 } 3599 3600 m_min_x = 2; 3601 m_min_y = 1; 3602 m_max_x = window.GetWidth() - 1; 3603 m_max_y = window.GetHeight() - 1; 3604 3605 window.Erase(); 3606 window.DrawTitleBox(window.GetName()); 3607 3608 if (display_content) { 3609 const int num_visible_rows = NumVisibleRows(); 3610 m_num_rows = 0; 3611 m_root.CalculateRowIndexes(m_num_rows); 3612 m_delegate_sp->TreeDelegateUpdateSelection(m_root, m_selected_row_idx, 3613 m_selected_item); 3614 3615 // If we unexpanded while having something selected our total number of 3616 // rows is less than the num visible rows, then make sure we show all the 3617 // rows by setting the first visible row accordingly. 3618 if (m_first_visible_row > 0 && m_num_rows < num_visible_rows) 3619 m_first_visible_row = 0; 3620 3621 // Make sure the selected row is always visible 3622 if (m_selected_row_idx < m_first_visible_row) 3623 m_first_visible_row = m_selected_row_idx; 3624 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) 3625 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; 3626 3627 int row_idx = 0; 3628 int num_rows_left = num_visible_rows; 3629 m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx, 3630 num_rows_left); 3631 // Get the selected row 3632 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 3633 } else { 3634 m_selected_item = nullptr; 3635 } 3636 3637 return true; // Drawing handled 3638 } 3639 3640 const char *WindowDelegateGetHelpText() override { 3641 return "Thread window keyboard shortcuts:"; 3642 } 3643 3644 KeyHelp *WindowDelegateGetKeyHelp() override { 3645 static curses::KeyHelp g_source_view_key_help[] = { 3646 {KEY_UP, "Select previous item"}, 3647 {KEY_DOWN, "Select next item"}, 3648 {KEY_RIGHT, "Expand the selected item"}, 3649 {KEY_LEFT, 3650 "Unexpand the selected item or select parent if not expanded"}, 3651 {KEY_PPAGE, "Page up"}, 3652 {KEY_NPAGE, "Page down"}, 3653 {'h', "Show help dialog"}, 3654 {' ', "Toggle item expansion"}, 3655 {',', "Page up"}, 3656 {'.', "Page down"}, 3657 {'\0', nullptr}}; 3658 return g_source_view_key_help; 3659 } 3660 3661 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 3662 switch (c) { 3663 case ',': 3664 case KEY_PPAGE: 3665 // Page up key 3666 if (m_first_visible_row > 0) { 3667 if (m_first_visible_row > m_max_y) 3668 m_first_visible_row -= m_max_y; 3669 else 3670 m_first_visible_row = 0; 3671 m_selected_row_idx = m_first_visible_row; 3672 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 3673 if (m_selected_item) 3674 m_selected_item->ItemWasSelected(); 3675 } 3676 return eKeyHandled; 3677 3678 case '.': 3679 case KEY_NPAGE: 3680 // Page down key 3681 if (m_num_rows > m_max_y) { 3682 if (m_first_visible_row + m_max_y < m_num_rows) { 3683 m_first_visible_row += m_max_y; 3684 m_selected_row_idx = m_first_visible_row; 3685 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 3686 if (m_selected_item) 3687 m_selected_item->ItemWasSelected(); 3688 } 3689 } 3690 return eKeyHandled; 3691 3692 case KEY_UP: 3693 if (m_selected_row_idx > 0) { 3694 --m_selected_row_idx; 3695 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 3696 if (m_selected_item) 3697 m_selected_item->ItemWasSelected(); 3698 } 3699 return eKeyHandled; 3700 3701 case KEY_DOWN: 3702 if (m_selected_row_idx + 1 < m_num_rows) { 3703 ++m_selected_row_idx; 3704 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 3705 if (m_selected_item) 3706 m_selected_item->ItemWasSelected(); 3707 } 3708 return eKeyHandled; 3709 3710 case KEY_RIGHT: 3711 if (m_selected_item) { 3712 if (!m_selected_item->IsExpanded()) 3713 m_selected_item->Expand(); 3714 } 3715 return eKeyHandled; 3716 3717 case KEY_LEFT: 3718 if (m_selected_item) { 3719 if (m_selected_item->IsExpanded()) 3720 m_selected_item->Unexpand(); 3721 else if (m_selected_item->GetParent()) { 3722 m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex(); 3723 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 3724 if (m_selected_item) 3725 m_selected_item->ItemWasSelected(); 3726 } 3727 } 3728 return eKeyHandled; 3729 3730 case ' ': 3731 // Toggle expansion state when SPACE is pressed 3732 if (m_selected_item) { 3733 if (m_selected_item->IsExpanded()) 3734 m_selected_item->Unexpand(); 3735 else 3736 m_selected_item->Expand(); 3737 } 3738 return eKeyHandled; 3739 3740 case 'h': 3741 window.CreateHelpSubwindow(); 3742 return eKeyHandled; 3743 3744 default: 3745 break; 3746 } 3747 return eKeyNotHandled; 3748 } 3749 3750 protected: 3751 Debugger &m_debugger; 3752 TreeDelegateSP m_delegate_sp; 3753 TreeItem m_root; 3754 TreeItem *m_selected_item; 3755 int m_num_rows; 3756 int m_selected_row_idx; 3757 int m_first_visible_row; 3758 int m_min_x; 3759 int m_min_y; 3760 int m_max_x; 3761 int m_max_y; 3762 }; 3763 3764 class FrameTreeDelegate : public TreeDelegate { 3765 public: 3766 FrameTreeDelegate() : TreeDelegate() { 3767 FormatEntity::Parse( 3768 "frame #${frame.index}: {${function.name}${function.pc-offset}}}", 3769 m_format); 3770 } 3771 3772 ~FrameTreeDelegate() override = default; 3773 3774 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 3775 Thread *thread = (Thread *)item.GetUserData(); 3776 if (thread) { 3777 const uint64_t frame_idx = item.GetIdentifier(); 3778 StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx); 3779 if (frame_sp) { 3780 StreamString strm; 3781 const SymbolContext &sc = 3782 frame_sp->GetSymbolContext(eSymbolContextEverything); 3783 ExecutionContext exe_ctx(frame_sp); 3784 if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr, 3785 nullptr, false, false)) { 3786 int right_pad = 1; 3787 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 3788 } 3789 } 3790 } 3791 } 3792 3793 void TreeDelegateGenerateChildren(TreeItem &item) override { 3794 // No children for frames yet... 3795 } 3796 3797 bool TreeDelegateItemSelected(TreeItem &item) override { 3798 Thread *thread = (Thread *)item.GetUserData(); 3799 if (thread) { 3800 thread->GetProcess()->GetThreadList().SetSelectedThreadByID( 3801 thread->GetID()); 3802 const uint64_t frame_idx = item.GetIdentifier(); 3803 thread->SetSelectedFrameByIndex(frame_idx); 3804 return true; 3805 } 3806 return false; 3807 } 3808 3809 protected: 3810 FormatEntity::Entry m_format; 3811 }; 3812 3813 class ThreadTreeDelegate : public TreeDelegate { 3814 public: 3815 ThreadTreeDelegate(Debugger &debugger) 3816 : TreeDelegate(), m_debugger(debugger), m_tid(LLDB_INVALID_THREAD_ID), 3817 m_stop_id(UINT32_MAX) { 3818 FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop " 3819 "reason = ${thread.stop-reason}}", 3820 m_format); 3821 } 3822 3823 ~ThreadTreeDelegate() override = default; 3824 3825 ProcessSP GetProcess() { 3826 return m_debugger.GetCommandInterpreter() 3827 .GetExecutionContext() 3828 .GetProcessSP(); 3829 } 3830 3831 ThreadSP GetThread(const TreeItem &item) { 3832 ProcessSP process_sp = GetProcess(); 3833 if (process_sp) 3834 return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier()); 3835 return ThreadSP(); 3836 } 3837 3838 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 3839 ThreadSP thread_sp = GetThread(item); 3840 if (thread_sp) { 3841 StreamString strm; 3842 ExecutionContext exe_ctx(thread_sp); 3843 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, 3844 nullptr, false, false)) { 3845 int right_pad = 1; 3846 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 3847 } 3848 } 3849 } 3850 3851 void TreeDelegateGenerateChildren(TreeItem &item) override { 3852 ProcessSP process_sp = GetProcess(); 3853 if (process_sp && process_sp->IsAlive()) { 3854 StateType state = process_sp->GetState(); 3855 if (StateIsStoppedState(state, true)) { 3856 ThreadSP thread_sp = GetThread(item); 3857 if (thread_sp) { 3858 if (m_stop_id == process_sp->GetStopID() && 3859 thread_sp->GetID() == m_tid) 3860 return; // Children are already up to date 3861 if (!m_frame_delegate_sp) { 3862 // Always expand the thread item the first time we show it 3863 m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>(); 3864 } 3865 3866 m_stop_id = process_sp->GetStopID(); 3867 m_tid = thread_sp->GetID(); 3868 3869 TreeItem t(&item, *m_frame_delegate_sp, false); 3870 size_t num_frames = thread_sp->GetStackFrameCount(); 3871 item.Resize(num_frames, t); 3872 for (size_t i = 0; i < num_frames; ++i) { 3873 item[i].SetUserData(thread_sp.get()); 3874 item[i].SetIdentifier(i); 3875 } 3876 } 3877 return; 3878 } 3879 } 3880 item.ClearChildren(); 3881 } 3882 3883 bool TreeDelegateItemSelected(TreeItem &item) override { 3884 ProcessSP process_sp = GetProcess(); 3885 if (process_sp && process_sp->IsAlive()) { 3886 StateType state = process_sp->GetState(); 3887 if (StateIsStoppedState(state, true)) { 3888 ThreadSP thread_sp = GetThread(item); 3889 if (thread_sp) { 3890 ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList(); 3891 std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex()); 3892 ThreadSP selected_thread_sp = thread_list.GetSelectedThread(); 3893 if (selected_thread_sp->GetID() != thread_sp->GetID()) { 3894 thread_list.SetSelectedThreadByID(thread_sp->GetID()); 3895 return true; 3896 } 3897 } 3898 } 3899 } 3900 return false; 3901 } 3902 3903 protected: 3904 Debugger &m_debugger; 3905 std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp; 3906 lldb::user_id_t m_tid; 3907 uint32_t m_stop_id; 3908 FormatEntity::Entry m_format; 3909 }; 3910 3911 class ThreadsTreeDelegate : public TreeDelegate { 3912 public: 3913 ThreadsTreeDelegate(Debugger &debugger) 3914 : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger), 3915 m_stop_id(UINT32_MAX), m_update_selection(false) { 3916 FormatEntity::Parse("process ${process.id}{, name = ${process.name}}", 3917 m_format); 3918 } 3919 3920 ~ThreadsTreeDelegate() override = default; 3921 3922 ProcessSP GetProcess() { 3923 return m_debugger.GetCommandInterpreter() 3924 .GetExecutionContext() 3925 .GetProcessSP(); 3926 } 3927 3928 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 3929 ProcessSP process_sp = GetProcess(); 3930 if (process_sp && process_sp->IsAlive()) { 3931 StreamString strm; 3932 ExecutionContext exe_ctx(process_sp); 3933 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, 3934 nullptr, false, false)) { 3935 int right_pad = 1; 3936 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 3937 } 3938 } 3939 } 3940 3941 void TreeDelegateGenerateChildren(TreeItem &item) override { 3942 ProcessSP process_sp = GetProcess(); 3943 m_update_selection = false; 3944 if (process_sp && process_sp->IsAlive()) { 3945 StateType state = process_sp->GetState(); 3946 if (StateIsStoppedState(state, true)) { 3947 const uint32_t stop_id = process_sp->GetStopID(); 3948 if (m_stop_id == stop_id) 3949 return; // Children are already up to date 3950 3951 m_stop_id = stop_id; 3952 m_update_selection = true; 3953 3954 if (!m_thread_delegate_sp) { 3955 // Always expand the thread item the first time we show it 3956 // item.Expand(); 3957 m_thread_delegate_sp = 3958 std::make_shared<ThreadTreeDelegate>(m_debugger); 3959 } 3960 3961 TreeItem t(&item, *m_thread_delegate_sp, false); 3962 ThreadList &threads = process_sp->GetThreadList(); 3963 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 3964 ThreadSP selected_thread = threads.GetSelectedThread(); 3965 size_t num_threads = threads.GetSize(); 3966 item.Resize(num_threads, t); 3967 for (size_t i = 0; i < num_threads; ++i) { 3968 ThreadSP thread = threads.GetThreadAtIndex(i); 3969 item[i].SetIdentifier(thread->GetID()); 3970 item[i].SetMightHaveChildren(true); 3971 if (selected_thread->GetID() == thread->GetID()) 3972 item[i].Expand(); 3973 } 3974 return; 3975 } 3976 } 3977 item.ClearChildren(); 3978 } 3979 3980 void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index, 3981 TreeItem *&selected_item) override { 3982 if (!m_update_selection) 3983 return; 3984 3985 ProcessSP process_sp = GetProcess(); 3986 if (!(process_sp && process_sp->IsAlive())) 3987 return; 3988 3989 StateType state = process_sp->GetState(); 3990 if (!StateIsStoppedState(state, true)) 3991 return; 3992 3993 ThreadList &threads = process_sp->GetThreadList(); 3994 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 3995 ThreadSP selected_thread = threads.GetSelectedThread(); 3996 size_t num_threads = threads.GetSize(); 3997 for (size_t i = 0; i < num_threads; ++i) { 3998 ThreadSP thread = threads.GetThreadAtIndex(i); 3999 if (selected_thread->GetID() == thread->GetID()) { 4000 selected_item = &root[i][thread->GetSelectedFrameIndex()]; 4001 selection_index = selected_item->GetRowIndex(); 4002 return; 4003 } 4004 } 4005 } 4006 4007 bool TreeDelegateItemSelected(TreeItem &item) override { return false; } 4008 4009 bool TreeDelegateExpandRootByDefault() override { return true; } 4010 4011 protected: 4012 std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp; 4013 Debugger &m_debugger; 4014 uint32_t m_stop_id; 4015 bool m_update_selection; 4016 FormatEntity::Entry m_format; 4017 }; 4018 4019 class ValueObjectListDelegate : public WindowDelegate { 4020 public: 4021 ValueObjectListDelegate() : m_rows() {} 4022 4023 ValueObjectListDelegate(ValueObjectList &valobj_list) 4024 : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0), 4025 m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) { 4026 SetValues(valobj_list); 4027 } 4028 4029 ~ValueObjectListDelegate() override = default; 4030 4031 void SetValues(ValueObjectList &valobj_list) { 4032 m_selected_row = nullptr; 4033 m_selected_row_idx = 0; 4034 m_first_visible_row = 0; 4035 m_num_rows = 0; 4036 m_rows.clear(); 4037 for (auto &valobj_sp : valobj_list.GetObjects()) 4038 m_rows.push_back(Row(valobj_sp, nullptr)); 4039 } 4040 4041 bool WindowDelegateDraw(Window &window, bool force) override { 4042 m_num_rows = 0; 4043 m_min_x = 2; 4044 m_min_y = 1; 4045 m_max_x = window.GetWidth() - 1; 4046 m_max_y = window.GetHeight() - 1; 4047 4048 window.Erase(); 4049 window.DrawTitleBox(window.GetName()); 4050 4051 const int num_visible_rows = NumVisibleRows(); 4052 const int num_rows = CalculateTotalNumberRows(m_rows); 4053 4054 // If we unexpanded while having something selected our total number of 4055 // rows is less than the num visible rows, then make sure we show all the 4056 // rows by setting the first visible row accordingly. 4057 if (m_first_visible_row > 0 && num_rows < num_visible_rows) 4058 m_first_visible_row = 0; 4059 4060 // Make sure the selected row is always visible 4061 if (m_selected_row_idx < m_first_visible_row) 4062 m_first_visible_row = m_selected_row_idx; 4063 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) 4064 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; 4065 4066 DisplayRows(window, m_rows, g_options); 4067 4068 // Get the selected row 4069 m_selected_row = GetRowForRowIndex(m_selected_row_idx); 4070 // Keep the cursor on the selected row so the highlight and the cursor are 4071 // always on the same line 4072 if (m_selected_row) 4073 window.MoveCursor(m_selected_row->x, m_selected_row->y); 4074 4075 return true; // Drawing handled 4076 } 4077 4078 KeyHelp *WindowDelegateGetKeyHelp() override { 4079 static curses::KeyHelp g_source_view_key_help[] = { 4080 {KEY_UP, "Select previous item"}, 4081 {KEY_DOWN, "Select next item"}, 4082 {KEY_RIGHT, "Expand selected item"}, 4083 {KEY_LEFT, "Unexpand selected item or select parent if not expanded"}, 4084 {KEY_PPAGE, "Page up"}, 4085 {KEY_NPAGE, "Page down"}, 4086 {'A', "Format as annotated address"}, 4087 {'b', "Format as binary"}, 4088 {'B', "Format as hex bytes with ASCII"}, 4089 {'c', "Format as character"}, 4090 {'d', "Format as a signed integer"}, 4091 {'D', "Format selected value using the default format for the type"}, 4092 {'f', "Format as float"}, 4093 {'h', "Show help dialog"}, 4094 {'i', "Format as instructions"}, 4095 {'o', "Format as octal"}, 4096 {'p', "Format as pointer"}, 4097 {'s', "Format as C string"}, 4098 {'t', "Toggle showing/hiding type names"}, 4099 {'u', "Format as an unsigned integer"}, 4100 {'x', "Format as hex"}, 4101 {'X', "Format as uppercase hex"}, 4102 {' ', "Toggle item expansion"}, 4103 {',', "Page up"}, 4104 {'.', "Page down"}, 4105 {'\0', nullptr}}; 4106 return g_source_view_key_help; 4107 } 4108 4109 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 4110 switch (c) { 4111 case 'x': 4112 case 'X': 4113 case 'o': 4114 case 's': 4115 case 'u': 4116 case 'd': 4117 case 'D': 4118 case 'i': 4119 case 'A': 4120 case 'p': 4121 case 'c': 4122 case 'b': 4123 case 'B': 4124 case 'f': 4125 // Change the format for the currently selected item 4126 if (m_selected_row) { 4127 auto valobj_sp = m_selected_row->value.GetSP(); 4128 if (valobj_sp) 4129 valobj_sp->SetFormat(FormatForChar(c)); 4130 } 4131 return eKeyHandled; 4132 4133 case 't': 4134 // Toggle showing type names 4135 g_options.show_types = !g_options.show_types; 4136 return eKeyHandled; 4137 4138 case ',': 4139 case KEY_PPAGE: 4140 // Page up key 4141 if (m_first_visible_row > 0) { 4142 if (static_cast<int>(m_first_visible_row) > m_max_y) 4143 m_first_visible_row -= m_max_y; 4144 else 4145 m_first_visible_row = 0; 4146 m_selected_row_idx = m_first_visible_row; 4147 } 4148 return eKeyHandled; 4149 4150 case '.': 4151 case KEY_NPAGE: 4152 // Page down key 4153 if (m_num_rows > static_cast<size_t>(m_max_y)) { 4154 if (m_first_visible_row + m_max_y < m_num_rows) { 4155 m_first_visible_row += m_max_y; 4156 m_selected_row_idx = m_first_visible_row; 4157 } 4158 } 4159 return eKeyHandled; 4160 4161 case KEY_UP: 4162 if (m_selected_row_idx > 0) 4163 --m_selected_row_idx; 4164 return eKeyHandled; 4165 4166 case KEY_DOWN: 4167 if (m_selected_row_idx + 1 < m_num_rows) 4168 ++m_selected_row_idx; 4169 return eKeyHandled; 4170 4171 case KEY_RIGHT: 4172 if (m_selected_row) { 4173 if (!m_selected_row->expanded) 4174 m_selected_row->Expand(); 4175 } 4176 return eKeyHandled; 4177 4178 case KEY_LEFT: 4179 if (m_selected_row) { 4180 if (m_selected_row->expanded) 4181 m_selected_row->Unexpand(); 4182 else if (m_selected_row->parent) 4183 m_selected_row_idx = m_selected_row->parent->row_idx; 4184 } 4185 return eKeyHandled; 4186 4187 case ' ': 4188 // Toggle expansion state when SPACE is pressed 4189 if (m_selected_row) { 4190 if (m_selected_row->expanded) 4191 m_selected_row->Unexpand(); 4192 else 4193 m_selected_row->Expand(); 4194 } 4195 return eKeyHandled; 4196 4197 case 'h': 4198 window.CreateHelpSubwindow(); 4199 return eKeyHandled; 4200 4201 default: 4202 break; 4203 } 4204 return eKeyNotHandled; 4205 } 4206 4207 protected: 4208 std::vector<Row> m_rows; 4209 Row *m_selected_row = nullptr; 4210 uint32_t m_selected_row_idx = 0; 4211 uint32_t m_first_visible_row = 0; 4212 uint32_t m_num_rows = 0; 4213 int m_min_x; 4214 int m_min_y; 4215 int m_max_x = 0; 4216 int m_max_y = 0; 4217 4218 static Format FormatForChar(int c) { 4219 switch (c) { 4220 case 'x': 4221 return eFormatHex; 4222 case 'X': 4223 return eFormatHexUppercase; 4224 case 'o': 4225 return eFormatOctal; 4226 case 's': 4227 return eFormatCString; 4228 case 'u': 4229 return eFormatUnsigned; 4230 case 'd': 4231 return eFormatDecimal; 4232 case 'D': 4233 return eFormatDefault; 4234 case 'i': 4235 return eFormatInstruction; 4236 case 'A': 4237 return eFormatAddressInfo; 4238 case 'p': 4239 return eFormatPointer; 4240 case 'c': 4241 return eFormatChar; 4242 case 'b': 4243 return eFormatBinary; 4244 case 'B': 4245 return eFormatBytesWithASCII; 4246 case 'f': 4247 return eFormatFloat; 4248 } 4249 return eFormatDefault; 4250 } 4251 4252 bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options, 4253 bool highlight, bool last_child) { 4254 ValueObject *valobj = row.value.GetSP().get(); 4255 4256 if (valobj == nullptr) 4257 return false; 4258 4259 const char *type_name = 4260 options.show_types ? valobj->GetTypeName().GetCString() : nullptr; 4261 const char *name = valobj->GetName().GetCString(); 4262 const char *value = valobj->GetValueAsCString(); 4263 const char *summary = valobj->GetSummaryAsCString(); 4264 4265 window.MoveCursor(row.x, row.y); 4266 4267 row.DrawTree(window); 4268 4269 if (highlight) 4270 window.AttributeOn(A_REVERSE); 4271 4272 if (type_name && type_name[0]) 4273 window.PrintfTruncated(1, "(%s) ", type_name); 4274 4275 if (name && name[0]) 4276 window.PutCStringTruncated(1, name); 4277 4278 attr_t changd_attr = 0; 4279 if (valobj->GetValueDidChange()) 4280 changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD; 4281 4282 if (value && value[0]) { 4283 window.PutCStringTruncated(1, " = "); 4284 if (changd_attr) 4285 window.AttributeOn(changd_attr); 4286 window.PutCStringTruncated(1, value); 4287 if (changd_attr) 4288 window.AttributeOff(changd_attr); 4289 } 4290 4291 if (summary && summary[0]) { 4292 window.PutCStringTruncated(1, " "); 4293 if (changd_attr) 4294 window.AttributeOn(changd_attr); 4295 window.PutCStringTruncated(1, summary); 4296 if (changd_attr) 4297 window.AttributeOff(changd_attr); 4298 } 4299 4300 if (highlight) 4301 window.AttributeOff(A_REVERSE); 4302 4303 return true; 4304 } 4305 4306 void DisplayRows(Window &window, std::vector<Row> &rows, 4307 DisplayOptions &options) { 4308 // > 0x25B7 4309 // \/ 0x25BD 4310 4311 bool window_is_active = window.IsActive(); 4312 for (auto &row : rows) { 4313 const bool last_child = row.parent && &rows[rows.size() - 1] == &row; 4314 // Save the row index in each Row structure 4315 row.row_idx = m_num_rows; 4316 if ((m_num_rows >= m_first_visible_row) && 4317 ((m_num_rows - m_first_visible_row) < 4318 static_cast<size_t>(NumVisibleRows()))) { 4319 row.x = m_min_x; 4320 row.y = m_num_rows - m_first_visible_row + 1; 4321 if (DisplayRowObject(window, row, options, 4322 window_is_active && 4323 m_num_rows == m_selected_row_idx, 4324 last_child)) { 4325 ++m_num_rows; 4326 } else { 4327 row.x = 0; 4328 row.y = 0; 4329 } 4330 } else { 4331 row.x = 0; 4332 row.y = 0; 4333 ++m_num_rows; 4334 } 4335 4336 auto &children = row.GetChildren(); 4337 if (row.expanded && !children.empty()) { 4338 DisplayRows(window, children, options); 4339 } 4340 } 4341 } 4342 4343 int CalculateTotalNumberRows(std::vector<Row> &rows) { 4344 int row_count = 0; 4345 for (auto &row : rows) { 4346 ++row_count; 4347 if (row.expanded) 4348 row_count += CalculateTotalNumberRows(row.GetChildren()); 4349 } 4350 return row_count; 4351 } 4352 4353 static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) { 4354 for (auto &row : rows) { 4355 if (row_index == 0) 4356 return &row; 4357 else { 4358 --row_index; 4359 auto &children = row.GetChildren(); 4360 if (row.expanded && !children.empty()) { 4361 Row *result = GetRowForRowIndexImpl(children, row_index); 4362 if (result) 4363 return result; 4364 } 4365 } 4366 } 4367 return nullptr; 4368 } 4369 4370 Row *GetRowForRowIndex(size_t row_index) { 4371 return GetRowForRowIndexImpl(m_rows, row_index); 4372 } 4373 4374 int NumVisibleRows() const { return m_max_y - m_min_y; } 4375 4376 static DisplayOptions g_options; 4377 }; 4378 4379 class FrameVariablesWindowDelegate : public ValueObjectListDelegate { 4380 public: 4381 FrameVariablesWindowDelegate(Debugger &debugger) 4382 : ValueObjectListDelegate(), m_debugger(debugger), 4383 m_frame_block(nullptr) {} 4384 4385 ~FrameVariablesWindowDelegate() override = default; 4386 4387 const char *WindowDelegateGetHelpText() override { 4388 return "Frame variable window keyboard shortcuts:"; 4389 } 4390 4391 bool WindowDelegateDraw(Window &window, bool force) override { 4392 ExecutionContext exe_ctx( 4393 m_debugger.GetCommandInterpreter().GetExecutionContext()); 4394 Process *process = exe_ctx.GetProcessPtr(); 4395 Block *frame_block = nullptr; 4396 StackFrame *frame = nullptr; 4397 4398 if (process) { 4399 StateType state = process->GetState(); 4400 if (StateIsStoppedState(state, true)) { 4401 frame = exe_ctx.GetFramePtr(); 4402 if (frame) 4403 frame_block = frame->GetFrameBlock(); 4404 } else if (StateIsRunningState(state)) { 4405 return true; // Don't do any updating when we are running 4406 } 4407 } 4408 4409 ValueObjectList local_values; 4410 if (frame_block) { 4411 // Only update the variables if they have changed 4412 if (m_frame_block != frame_block) { 4413 m_frame_block = frame_block; 4414 4415 VariableList *locals = frame->GetVariableList(true); 4416 if (locals) { 4417 const DynamicValueType use_dynamic = eDynamicDontRunTarget; 4418 for (const VariableSP &local_sp : *locals) { 4419 ValueObjectSP value_sp = 4420 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic); 4421 if (value_sp) { 4422 ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue(); 4423 if (synthetic_value_sp) 4424 local_values.Append(synthetic_value_sp); 4425 else 4426 local_values.Append(value_sp); 4427 } 4428 } 4429 // Update the values 4430 SetValues(local_values); 4431 } 4432 } 4433 } else { 4434 m_frame_block = nullptr; 4435 // Update the values with an empty list if there is no frame 4436 SetValues(local_values); 4437 } 4438 4439 return ValueObjectListDelegate::WindowDelegateDraw(window, force); 4440 } 4441 4442 protected: 4443 Debugger &m_debugger; 4444 Block *m_frame_block; 4445 }; 4446 4447 class RegistersWindowDelegate : public ValueObjectListDelegate { 4448 public: 4449 RegistersWindowDelegate(Debugger &debugger) 4450 : ValueObjectListDelegate(), m_debugger(debugger) {} 4451 4452 ~RegistersWindowDelegate() override = default; 4453 4454 const char *WindowDelegateGetHelpText() override { 4455 return "Register window keyboard shortcuts:"; 4456 } 4457 4458 bool WindowDelegateDraw(Window &window, bool force) override { 4459 ExecutionContext exe_ctx( 4460 m_debugger.GetCommandInterpreter().GetExecutionContext()); 4461 StackFrame *frame = exe_ctx.GetFramePtr(); 4462 4463 ValueObjectList value_list; 4464 if (frame) { 4465 if (frame->GetStackID() != m_stack_id) { 4466 m_stack_id = frame->GetStackID(); 4467 RegisterContextSP reg_ctx(frame->GetRegisterContext()); 4468 if (reg_ctx) { 4469 const uint32_t num_sets = reg_ctx->GetRegisterSetCount(); 4470 for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) { 4471 value_list.Append( 4472 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx)); 4473 } 4474 } 4475 SetValues(value_list); 4476 } 4477 } else { 4478 Process *process = exe_ctx.GetProcessPtr(); 4479 if (process && process->IsAlive()) 4480 return true; // Don't do any updating if we are running 4481 else { 4482 // Update the values with an empty list if there is no process or the 4483 // process isn't alive anymore 4484 SetValues(value_list); 4485 } 4486 } 4487 return ValueObjectListDelegate::WindowDelegateDraw(window, force); 4488 } 4489 4490 protected: 4491 Debugger &m_debugger; 4492 StackID m_stack_id; 4493 }; 4494 4495 static const char *CursesKeyToCString(int ch) { 4496 static char g_desc[32]; 4497 if (ch >= KEY_F0 && ch < KEY_F0 + 64) { 4498 snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0); 4499 return g_desc; 4500 } 4501 switch (ch) { 4502 case KEY_DOWN: 4503 return "down"; 4504 case KEY_UP: 4505 return "up"; 4506 case KEY_LEFT: 4507 return "left"; 4508 case KEY_RIGHT: 4509 return "right"; 4510 case KEY_HOME: 4511 return "home"; 4512 case KEY_BACKSPACE: 4513 return "backspace"; 4514 case KEY_DL: 4515 return "delete-line"; 4516 case KEY_IL: 4517 return "insert-line"; 4518 case KEY_DC: 4519 return "delete-char"; 4520 case KEY_IC: 4521 return "insert-char"; 4522 case KEY_CLEAR: 4523 return "clear"; 4524 case KEY_EOS: 4525 return "clear-to-eos"; 4526 case KEY_EOL: 4527 return "clear-to-eol"; 4528 case KEY_SF: 4529 return "scroll-forward"; 4530 case KEY_SR: 4531 return "scroll-backward"; 4532 case KEY_NPAGE: 4533 return "page-down"; 4534 case KEY_PPAGE: 4535 return "page-up"; 4536 case KEY_STAB: 4537 return "set-tab"; 4538 case KEY_CTAB: 4539 return "clear-tab"; 4540 case KEY_CATAB: 4541 return "clear-all-tabs"; 4542 case KEY_ENTER: 4543 return "enter"; 4544 case KEY_PRINT: 4545 return "print"; 4546 case KEY_LL: 4547 return "lower-left key"; 4548 case KEY_A1: 4549 return "upper left of keypad"; 4550 case KEY_A3: 4551 return "upper right of keypad"; 4552 case KEY_B2: 4553 return "center of keypad"; 4554 case KEY_C1: 4555 return "lower left of keypad"; 4556 case KEY_C3: 4557 return "lower right of keypad"; 4558 case KEY_BTAB: 4559 return "back-tab key"; 4560 case KEY_BEG: 4561 return "begin key"; 4562 case KEY_CANCEL: 4563 return "cancel key"; 4564 case KEY_CLOSE: 4565 return "close key"; 4566 case KEY_COMMAND: 4567 return "command key"; 4568 case KEY_COPY: 4569 return "copy key"; 4570 case KEY_CREATE: 4571 return "create key"; 4572 case KEY_END: 4573 return "end key"; 4574 case KEY_EXIT: 4575 return "exit key"; 4576 case KEY_FIND: 4577 return "find key"; 4578 case KEY_HELP: 4579 return "help key"; 4580 case KEY_MARK: 4581 return "mark key"; 4582 case KEY_MESSAGE: 4583 return "message key"; 4584 case KEY_MOVE: 4585 return "move key"; 4586 case KEY_NEXT: 4587 return "next key"; 4588 case KEY_OPEN: 4589 return "open key"; 4590 case KEY_OPTIONS: 4591 return "options key"; 4592 case KEY_PREVIOUS: 4593 return "previous key"; 4594 case KEY_REDO: 4595 return "redo key"; 4596 case KEY_REFERENCE: 4597 return "reference key"; 4598 case KEY_REFRESH: 4599 return "refresh key"; 4600 case KEY_REPLACE: 4601 return "replace key"; 4602 case KEY_RESTART: 4603 return "restart key"; 4604 case KEY_RESUME: 4605 return "resume key"; 4606 case KEY_SAVE: 4607 return "save key"; 4608 case KEY_SBEG: 4609 return "shifted begin key"; 4610 case KEY_SCANCEL: 4611 return "shifted cancel key"; 4612 case KEY_SCOMMAND: 4613 return "shifted command key"; 4614 case KEY_SCOPY: 4615 return "shifted copy key"; 4616 case KEY_SCREATE: 4617 return "shifted create key"; 4618 case KEY_SDC: 4619 return "shifted delete-character key"; 4620 case KEY_SDL: 4621 return "shifted delete-line key"; 4622 case KEY_SELECT: 4623 return "select key"; 4624 case KEY_SEND: 4625 return "shifted end key"; 4626 case KEY_SEOL: 4627 return "shifted clear-to-end-of-line key"; 4628 case KEY_SEXIT: 4629 return "shifted exit key"; 4630 case KEY_SFIND: 4631 return "shifted find key"; 4632 case KEY_SHELP: 4633 return "shifted help key"; 4634 case KEY_SHOME: 4635 return "shifted home key"; 4636 case KEY_SIC: 4637 return "shifted insert-character key"; 4638 case KEY_SLEFT: 4639 return "shifted left-arrow key"; 4640 case KEY_SMESSAGE: 4641 return "shifted message key"; 4642 case KEY_SMOVE: 4643 return "shifted move key"; 4644 case KEY_SNEXT: 4645 return "shifted next key"; 4646 case KEY_SOPTIONS: 4647 return "shifted options key"; 4648 case KEY_SPREVIOUS: 4649 return "shifted previous key"; 4650 case KEY_SPRINT: 4651 return "shifted print key"; 4652 case KEY_SREDO: 4653 return "shifted redo key"; 4654 case KEY_SREPLACE: 4655 return "shifted replace key"; 4656 case KEY_SRIGHT: 4657 return "shifted right-arrow key"; 4658 case KEY_SRSUME: 4659 return "shifted resume key"; 4660 case KEY_SSAVE: 4661 return "shifted save key"; 4662 case KEY_SSUSPEND: 4663 return "shifted suspend key"; 4664 case KEY_SUNDO: 4665 return "shifted undo key"; 4666 case KEY_SUSPEND: 4667 return "suspend key"; 4668 case KEY_UNDO: 4669 return "undo key"; 4670 case KEY_MOUSE: 4671 return "Mouse event has occurred"; 4672 case KEY_RESIZE: 4673 return "Terminal resize event"; 4674 #ifdef KEY_EVENT 4675 case KEY_EVENT: 4676 return "We were interrupted by an event"; 4677 #endif 4678 case KEY_RETURN: 4679 return "return"; 4680 case ' ': 4681 return "space"; 4682 case '\t': 4683 return "tab"; 4684 case KEY_ESCAPE: 4685 return "escape"; 4686 default: 4687 if (llvm::isPrint(ch)) 4688 snprintf(g_desc, sizeof(g_desc), "%c", ch); 4689 else 4690 snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch); 4691 return g_desc; 4692 } 4693 return nullptr; 4694 } 4695 4696 HelpDialogDelegate::HelpDialogDelegate(const char *text, 4697 KeyHelp *key_help_array) 4698 : m_text(), m_first_visible_line(0) { 4699 if (text && text[0]) { 4700 m_text.SplitIntoLines(text); 4701 m_text.AppendString(""); 4702 } 4703 if (key_help_array) { 4704 for (KeyHelp *key = key_help_array; key->ch; ++key) { 4705 StreamString key_description; 4706 key_description.Printf("%10s - %s", CursesKeyToCString(key->ch), 4707 key->description); 4708 m_text.AppendString(key_description.GetString()); 4709 } 4710 } 4711 } 4712 4713 HelpDialogDelegate::~HelpDialogDelegate() = default; 4714 4715 bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) { 4716 window.Erase(); 4717 const int window_height = window.GetHeight(); 4718 int x = 2; 4719 int y = 1; 4720 const int min_y = y; 4721 const int max_y = window_height - 1 - y; 4722 const size_t num_visible_lines = max_y - min_y + 1; 4723 const size_t num_lines = m_text.GetSize(); 4724 const char *bottom_message; 4725 if (num_lines <= num_visible_lines) 4726 bottom_message = "Press any key to exit"; 4727 else 4728 bottom_message = "Use arrows to scroll, any other key to exit"; 4729 window.DrawTitleBox(window.GetName(), bottom_message); 4730 while (y <= max_y) { 4731 window.MoveCursor(x, y); 4732 window.PutCStringTruncated( 4733 1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y)); 4734 ++y; 4735 } 4736 return true; 4737 } 4738 4739 HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window, 4740 int key) { 4741 bool done = false; 4742 const size_t num_lines = m_text.GetSize(); 4743 const size_t num_visible_lines = window.GetHeight() - 2; 4744 4745 if (num_lines <= num_visible_lines) { 4746 done = true; 4747 // If we have all lines visible and don't need scrolling, then any key 4748 // press will cause us to exit 4749 } else { 4750 switch (key) { 4751 case KEY_UP: 4752 if (m_first_visible_line > 0) 4753 --m_first_visible_line; 4754 break; 4755 4756 case KEY_DOWN: 4757 if (m_first_visible_line + num_visible_lines < num_lines) 4758 ++m_first_visible_line; 4759 break; 4760 4761 case KEY_PPAGE: 4762 case ',': 4763 if (m_first_visible_line > 0) { 4764 if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines) 4765 m_first_visible_line -= num_visible_lines; 4766 else 4767 m_first_visible_line = 0; 4768 } 4769 break; 4770 4771 case KEY_NPAGE: 4772 case '.': 4773 if (m_first_visible_line + num_visible_lines < num_lines) { 4774 m_first_visible_line += num_visible_lines; 4775 if (static_cast<size_t>(m_first_visible_line) > num_lines) 4776 m_first_visible_line = num_lines - num_visible_lines; 4777 } 4778 break; 4779 4780 default: 4781 done = true; 4782 break; 4783 } 4784 } 4785 if (done) 4786 window.GetParent()->RemoveSubWindow(&window); 4787 return eKeyHandled; 4788 } 4789 4790 class ApplicationDelegate : public WindowDelegate, public MenuDelegate { 4791 public: 4792 enum { 4793 eMenuID_LLDB = 1, 4794 eMenuID_LLDBAbout, 4795 eMenuID_LLDBExit, 4796 4797 eMenuID_Target, 4798 eMenuID_TargetCreate, 4799 eMenuID_TargetDelete, 4800 4801 eMenuID_Process, 4802 eMenuID_ProcessAttach, 4803 eMenuID_ProcessDetachResume, 4804 eMenuID_ProcessDetachSuspended, 4805 eMenuID_ProcessLaunch, 4806 eMenuID_ProcessContinue, 4807 eMenuID_ProcessHalt, 4808 eMenuID_ProcessKill, 4809 4810 eMenuID_Thread, 4811 eMenuID_ThreadStepIn, 4812 eMenuID_ThreadStepOver, 4813 eMenuID_ThreadStepOut, 4814 4815 eMenuID_View, 4816 eMenuID_ViewBacktrace, 4817 eMenuID_ViewRegisters, 4818 eMenuID_ViewSource, 4819 eMenuID_ViewVariables, 4820 4821 eMenuID_Help, 4822 eMenuID_HelpGUIHelp 4823 }; 4824 4825 ApplicationDelegate(Application &app, Debugger &debugger) 4826 : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {} 4827 4828 ~ApplicationDelegate() override = default; 4829 4830 bool WindowDelegateDraw(Window &window, bool force) override { 4831 return false; // Drawing not handled, let standard window drawing happen 4832 } 4833 4834 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { 4835 switch (key) { 4836 case '\t': 4837 window.SelectNextWindowAsActive(); 4838 return eKeyHandled; 4839 4840 case KEY_SHIFT_TAB: 4841 window.SelectPreviousWindowAsActive(); 4842 return eKeyHandled; 4843 4844 case 'h': 4845 window.CreateHelpSubwindow(); 4846 return eKeyHandled; 4847 4848 case KEY_ESCAPE: 4849 return eQuitApplication; 4850 4851 default: 4852 break; 4853 } 4854 return eKeyNotHandled; 4855 } 4856 4857 const char *WindowDelegateGetHelpText() override { 4858 return "Welcome to the LLDB curses GUI.\n\n" 4859 "Press the TAB key to change the selected view.\n" 4860 "Each view has its own keyboard shortcuts, press 'h' to open a " 4861 "dialog to display them.\n\n" 4862 "Common key bindings for all views:"; 4863 } 4864 4865 KeyHelp *WindowDelegateGetKeyHelp() override { 4866 static curses::KeyHelp g_source_view_key_help[] = { 4867 {'\t', "Select next view"}, 4868 {KEY_BTAB, "Select previous view"}, 4869 {'h', "Show help dialog with view specific key bindings"}, 4870 {',', "Page up"}, 4871 {'.', "Page down"}, 4872 {KEY_UP, "Select previous"}, 4873 {KEY_DOWN, "Select next"}, 4874 {KEY_LEFT, "Unexpand or select parent"}, 4875 {KEY_RIGHT, "Expand"}, 4876 {KEY_PPAGE, "Page up"}, 4877 {KEY_NPAGE, "Page down"}, 4878 {'\0', nullptr}}; 4879 return g_source_view_key_help; 4880 } 4881 4882 MenuActionResult MenuDelegateAction(Menu &menu) override { 4883 switch (menu.GetIdentifier()) { 4884 case eMenuID_ThreadStepIn: { 4885 ExecutionContext exe_ctx = 4886 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4887 if (exe_ctx.HasThreadScope()) { 4888 Process *process = exe_ctx.GetProcessPtr(); 4889 if (process && process->IsAlive() && 4890 StateIsStoppedState(process->GetState(), true)) 4891 exe_ctx.GetThreadRef().StepIn(true); 4892 } 4893 } 4894 return MenuActionResult::Handled; 4895 4896 case eMenuID_ThreadStepOut: { 4897 ExecutionContext exe_ctx = 4898 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4899 if (exe_ctx.HasThreadScope()) { 4900 Process *process = exe_ctx.GetProcessPtr(); 4901 if (process && process->IsAlive() && 4902 StateIsStoppedState(process->GetState(), true)) 4903 exe_ctx.GetThreadRef().StepOut(); 4904 } 4905 } 4906 return MenuActionResult::Handled; 4907 4908 case eMenuID_ThreadStepOver: { 4909 ExecutionContext exe_ctx = 4910 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4911 if (exe_ctx.HasThreadScope()) { 4912 Process *process = exe_ctx.GetProcessPtr(); 4913 if (process && process->IsAlive() && 4914 StateIsStoppedState(process->GetState(), true)) 4915 exe_ctx.GetThreadRef().StepOver(true); 4916 } 4917 } 4918 return MenuActionResult::Handled; 4919 4920 case eMenuID_ProcessAttach: { 4921 WindowSP main_window_sp = m_app.GetMainWindow(); 4922 FormDelegateSP form_delegate_sp = FormDelegateSP( 4923 new ProcessAttachFormDelegate(m_debugger, main_window_sp)); 4924 Rect bounds = main_window_sp->GetCenteredRect(80, 22); 4925 WindowSP form_window_sp = main_window_sp->CreateSubWindow( 4926 form_delegate_sp->GetName().c_str(), bounds, true); 4927 WindowDelegateSP window_delegate_sp = 4928 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); 4929 form_window_sp->SetDelegate(window_delegate_sp); 4930 return MenuActionResult::Handled; 4931 } 4932 4933 case eMenuID_ProcessContinue: { 4934 ExecutionContext exe_ctx = 4935 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4936 if (exe_ctx.HasProcessScope()) { 4937 Process *process = exe_ctx.GetProcessPtr(); 4938 if (process && process->IsAlive() && 4939 StateIsStoppedState(process->GetState(), true)) 4940 process->Resume(); 4941 } 4942 } 4943 return MenuActionResult::Handled; 4944 4945 case eMenuID_ProcessKill: { 4946 ExecutionContext exe_ctx = 4947 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4948 if (exe_ctx.HasProcessScope()) { 4949 Process *process = exe_ctx.GetProcessPtr(); 4950 if (process && process->IsAlive()) 4951 process->Destroy(false); 4952 } 4953 } 4954 return MenuActionResult::Handled; 4955 4956 case eMenuID_ProcessHalt: { 4957 ExecutionContext exe_ctx = 4958 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4959 if (exe_ctx.HasProcessScope()) { 4960 Process *process = exe_ctx.GetProcessPtr(); 4961 if (process && process->IsAlive()) 4962 process->Halt(); 4963 } 4964 } 4965 return MenuActionResult::Handled; 4966 4967 case eMenuID_ProcessDetachResume: 4968 case eMenuID_ProcessDetachSuspended: { 4969 ExecutionContext exe_ctx = 4970 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4971 if (exe_ctx.HasProcessScope()) { 4972 Process *process = exe_ctx.GetProcessPtr(); 4973 if (process && process->IsAlive()) 4974 process->Detach(menu.GetIdentifier() == 4975 eMenuID_ProcessDetachSuspended); 4976 } 4977 } 4978 return MenuActionResult::Handled; 4979 4980 case eMenuID_Process: { 4981 // Populate the menu with all of the threads if the process is stopped 4982 // when the Process menu gets selected and is about to display its 4983 // submenu. 4984 Menus &submenus = menu.GetSubmenus(); 4985 ExecutionContext exe_ctx = 4986 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4987 Process *process = exe_ctx.GetProcessPtr(); 4988 if (process && process->IsAlive() && 4989 StateIsStoppedState(process->GetState(), true)) { 4990 if (submenus.size() == 7) 4991 menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 4992 else if (submenus.size() > 8) 4993 submenus.erase(submenus.begin() + 8, submenus.end()); 4994 4995 ThreadList &threads = process->GetThreadList(); 4996 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 4997 size_t num_threads = threads.GetSize(); 4998 for (size_t i = 0; i < num_threads; ++i) { 4999 ThreadSP thread_sp = threads.GetThreadAtIndex(i); 5000 char menu_char = '\0'; 5001 if (i < 9) 5002 menu_char = '1' + i; 5003 StreamString thread_menu_title; 5004 thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID()); 5005 const char *thread_name = thread_sp->GetName(); 5006 if (thread_name && thread_name[0]) 5007 thread_menu_title.Printf(" %s", thread_name); 5008 else { 5009 const char *queue_name = thread_sp->GetQueueName(); 5010 if (queue_name && queue_name[0]) 5011 thread_menu_title.Printf(" %s", queue_name); 5012 } 5013 menu.AddSubmenu( 5014 MenuSP(new Menu(thread_menu_title.GetString().str().c_str(), 5015 nullptr, menu_char, thread_sp->GetID()))); 5016 } 5017 } else if (submenus.size() > 7) { 5018 // Remove the separator and any other thread submenu items that were 5019 // previously added 5020 submenus.erase(submenus.begin() + 7, submenus.end()); 5021 } 5022 // Since we are adding and removing items we need to recalculate the name 5023 // lengths 5024 menu.RecalculateNameLengths(); 5025 } 5026 return MenuActionResult::Handled; 5027 5028 case eMenuID_ViewVariables: { 5029 WindowSP main_window_sp = m_app.GetMainWindow(); 5030 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 5031 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 5032 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 5033 const Rect source_bounds = source_window_sp->GetBounds(); 5034 5035 if (variables_window_sp) { 5036 const Rect variables_bounds = variables_window_sp->GetBounds(); 5037 5038 main_window_sp->RemoveSubWindow(variables_window_sp.get()); 5039 5040 if (registers_window_sp) { 5041 // We have a registers window, so give all the area back to the 5042 // registers window 5043 Rect registers_bounds = variables_bounds; 5044 registers_bounds.size.width = source_bounds.size.width; 5045 registers_window_sp->SetBounds(registers_bounds); 5046 } else { 5047 // We have no registers window showing so give the bottom area back 5048 // to the source view 5049 source_window_sp->Resize(source_bounds.size.width, 5050 source_bounds.size.height + 5051 variables_bounds.size.height); 5052 } 5053 } else { 5054 Rect new_variables_rect; 5055 if (registers_window_sp) { 5056 // We have a registers window so split the area of the registers 5057 // window into two columns where the left hand side will be the 5058 // variables and the right hand side will be the registers 5059 const Rect variables_bounds = registers_window_sp->GetBounds(); 5060 Rect new_registers_rect; 5061 variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect, 5062 new_registers_rect); 5063 registers_window_sp->SetBounds(new_registers_rect); 5064 } else { 5065 // No registers window, grab the bottom part of the source window 5066 Rect new_source_rect; 5067 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, 5068 new_variables_rect); 5069 source_window_sp->SetBounds(new_source_rect); 5070 } 5071 WindowSP new_window_sp = main_window_sp->CreateSubWindow( 5072 "Variables", new_variables_rect, false); 5073 new_window_sp->SetDelegate( 5074 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 5075 } 5076 touchwin(stdscr); 5077 } 5078 return MenuActionResult::Handled; 5079 5080 case eMenuID_ViewRegisters: { 5081 WindowSP main_window_sp = m_app.GetMainWindow(); 5082 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 5083 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 5084 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 5085 const Rect source_bounds = source_window_sp->GetBounds(); 5086 5087 if (registers_window_sp) { 5088 if (variables_window_sp) { 5089 const Rect variables_bounds = variables_window_sp->GetBounds(); 5090 5091 // We have a variables window, so give all the area back to the 5092 // variables window 5093 variables_window_sp->Resize(variables_bounds.size.width + 5094 registers_window_sp->GetWidth(), 5095 variables_bounds.size.height); 5096 } else { 5097 // We have no variables window showing so give the bottom area back 5098 // to the source view 5099 source_window_sp->Resize(source_bounds.size.width, 5100 source_bounds.size.height + 5101 registers_window_sp->GetHeight()); 5102 } 5103 main_window_sp->RemoveSubWindow(registers_window_sp.get()); 5104 } else { 5105 Rect new_regs_rect; 5106 if (variables_window_sp) { 5107 // We have a variables window, split it into two columns where the 5108 // left hand side will be the variables and the right hand side will 5109 // be the registers 5110 const Rect variables_bounds = variables_window_sp->GetBounds(); 5111 Rect new_vars_rect; 5112 variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect, 5113 new_regs_rect); 5114 variables_window_sp->SetBounds(new_vars_rect); 5115 } else { 5116 // No variables window, grab the bottom part of the source window 5117 Rect new_source_rect; 5118 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, 5119 new_regs_rect); 5120 source_window_sp->SetBounds(new_source_rect); 5121 } 5122 WindowSP new_window_sp = 5123 main_window_sp->CreateSubWindow("Registers", new_regs_rect, false); 5124 new_window_sp->SetDelegate( 5125 WindowDelegateSP(new RegistersWindowDelegate(m_debugger))); 5126 } 5127 touchwin(stdscr); 5128 } 5129 return MenuActionResult::Handled; 5130 5131 case eMenuID_HelpGUIHelp: 5132 m_app.GetMainWindow()->CreateHelpSubwindow(); 5133 return MenuActionResult::Handled; 5134 5135 default: 5136 break; 5137 } 5138 5139 return MenuActionResult::NotHandled; 5140 } 5141 5142 protected: 5143 Application &m_app; 5144 Debugger &m_debugger; 5145 }; 5146 5147 class StatusBarWindowDelegate : public WindowDelegate { 5148 public: 5149 StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) { 5150 FormatEntity::Parse("Thread: ${thread.id%tid}", m_format); 5151 } 5152 5153 ~StatusBarWindowDelegate() override = default; 5154 5155 bool WindowDelegateDraw(Window &window, bool force) override { 5156 ExecutionContext exe_ctx = 5157 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5158 Process *process = exe_ctx.GetProcessPtr(); 5159 Thread *thread = exe_ctx.GetThreadPtr(); 5160 StackFrame *frame = exe_ctx.GetFramePtr(); 5161 window.Erase(); 5162 window.SetBackground(BlackOnWhite); 5163 window.MoveCursor(0, 0); 5164 if (process) { 5165 const StateType state = process->GetState(); 5166 window.Printf("Process: %5" PRIu64 " %10s", process->GetID(), 5167 StateAsCString(state)); 5168 5169 if (StateIsStoppedState(state, true)) { 5170 StreamString strm; 5171 if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, 5172 nullptr, nullptr, false, false)) { 5173 window.MoveCursor(40, 0); 5174 window.PutCStringTruncated(1, strm.GetString().str().c_str()); 5175 } 5176 5177 window.MoveCursor(60, 0); 5178 if (frame) 5179 window.Printf("Frame: %3u PC = 0x%16.16" PRIx64, 5180 frame->GetFrameIndex(), 5181 frame->GetFrameCodeAddress().GetOpcodeLoadAddress( 5182 exe_ctx.GetTargetPtr())); 5183 } else if (state == eStateExited) { 5184 const char *exit_desc = process->GetExitDescription(); 5185 const int exit_status = process->GetExitStatus(); 5186 if (exit_desc && exit_desc[0]) 5187 window.Printf(" with status = %i (%s)", exit_status, exit_desc); 5188 else 5189 window.Printf(" with status = %i", exit_status); 5190 } 5191 } 5192 return true; 5193 } 5194 5195 protected: 5196 Debugger &m_debugger; 5197 FormatEntity::Entry m_format; 5198 }; 5199 5200 class SourceFileWindowDelegate : public WindowDelegate { 5201 public: 5202 SourceFileWindowDelegate(Debugger &debugger) 5203 : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(), 5204 m_disassembly_scope(nullptr), m_disassembly_sp(), m_disassembly_range(), 5205 m_title(), m_tid(LLDB_INVALID_THREAD_ID), m_line_width(4), 5206 m_selected_line(0), m_pc_line(0), m_stop_id(0), m_frame_idx(UINT32_MAX), 5207 m_first_visible_line(0), m_first_visible_column(0), m_min_x(0), 5208 m_min_y(0), m_max_x(0), m_max_y(0) {} 5209 5210 ~SourceFileWindowDelegate() override = default; 5211 5212 void Update(const SymbolContext &sc) { m_sc = sc; } 5213 5214 uint32_t NumVisibleLines() const { return m_max_y - m_min_y; } 5215 5216 const char *WindowDelegateGetHelpText() override { 5217 return "Source/Disassembly window keyboard shortcuts:"; 5218 } 5219 5220 KeyHelp *WindowDelegateGetKeyHelp() override { 5221 static curses::KeyHelp g_source_view_key_help[] = { 5222 {KEY_RETURN, "Run to selected line with one shot breakpoint"}, 5223 {KEY_UP, "Select previous source line"}, 5224 {KEY_DOWN, "Select next source line"}, 5225 {KEY_LEFT, "Scroll to the left"}, 5226 {KEY_RIGHT, "Scroll to the right"}, 5227 {KEY_PPAGE, "Page up"}, 5228 {KEY_NPAGE, "Page down"}, 5229 {'b', "Set breakpoint on selected source/disassembly line"}, 5230 {'c', "Continue process"}, 5231 {'D', "Detach with process suspended"}, 5232 {'h', "Show help dialog"}, 5233 {'n', "Step over (source line)"}, 5234 {'N', "Step over (single instruction)"}, 5235 {'f', "Step out (finish)"}, 5236 {'s', "Step in (source line)"}, 5237 {'S', "Step in (single instruction)"}, 5238 {'u', "Frame up"}, 5239 {'d', "Frame down"}, 5240 {',', "Page up"}, 5241 {'.', "Page down"}, 5242 {'\0', nullptr}}; 5243 return g_source_view_key_help; 5244 } 5245 5246 bool WindowDelegateDraw(Window &window, bool force) override { 5247 ExecutionContext exe_ctx = 5248 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5249 Process *process = exe_ctx.GetProcessPtr(); 5250 Thread *thread = nullptr; 5251 5252 bool update_location = false; 5253 if (process) { 5254 StateType state = process->GetState(); 5255 if (StateIsStoppedState(state, true)) { 5256 // We are stopped, so it is ok to 5257 update_location = true; 5258 } 5259 } 5260 5261 m_min_x = 1; 5262 m_min_y = 2; 5263 m_max_x = window.GetMaxX() - 1; 5264 m_max_y = window.GetMaxY() - 1; 5265 5266 const uint32_t num_visible_lines = NumVisibleLines(); 5267 StackFrameSP frame_sp; 5268 bool set_selected_line_to_pc = false; 5269 5270 if (update_location) { 5271 const bool process_alive = process ? process->IsAlive() : false; 5272 bool thread_changed = false; 5273 if (process_alive) { 5274 thread = exe_ctx.GetThreadPtr(); 5275 if (thread) { 5276 frame_sp = thread->GetSelectedFrame(); 5277 auto tid = thread->GetID(); 5278 thread_changed = tid != m_tid; 5279 m_tid = tid; 5280 } else { 5281 if (m_tid != LLDB_INVALID_THREAD_ID) { 5282 thread_changed = true; 5283 m_tid = LLDB_INVALID_THREAD_ID; 5284 } 5285 } 5286 } 5287 const uint32_t stop_id = process ? process->GetStopID() : 0; 5288 const bool stop_id_changed = stop_id != m_stop_id; 5289 bool frame_changed = false; 5290 m_stop_id = stop_id; 5291 m_title.Clear(); 5292 if (frame_sp) { 5293 m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything); 5294 if (m_sc.module_sp) { 5295 m_title.Printf( 5296 "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString()); 5297 ConstString func_name = m_sc.GetFunctionName(); 5298 if (func_name) 5299 m_title.Printf("`%s", func_name.GetCString()); 5300 } 5301 const uint32_t frame_idx = frame_sp->GetFrameIndex(); 5302 frame_changed = frame_idx != m_frame_idx; 5303 m_frame_idx = frame_idx; 5304 } else { 5305 m_sc.Clear(true); 5306 frame_changed = m_frame_idx != UINT32_MAX; 5307 m_frame_idx = UINT32_MAX; 5308 } 5309 5310 const bool context_changed = 5311 thread_changed || frame_changed || stop_id_changed; 5312 5313 if (process_alive) { 5314 if (m_sc.line_entry.IsValid()) { 5315 m_pc_line = m_sc.line_entry.line; 5316 if (m_pc_line != UINT32_MAX) 5317 --m_pc_line; // Convert to zero based line number... 5318 // Update the selected line if the stop ID changed... 5319 if (context_changed) 5320 m_selected_line = m_pc_line; 5321 5322 if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) { 5323 // Same file, nothing to do, we should either have the lines or not 5324 // (source file missing) 5325 if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) { 5326 if (m_selected_line >= m_first_visible_line + num_visible_lines) 5327 m_first_visible_line = m_selected_line - 10; 5328 } else { 5329 if (m_selected_line > 10) 5330 m_first_visible_line = m_selected_line - 10; 5331 else 5332 m_first_visible_line = 0; 5333 } 5334 } else { 5335 // File changed, set selected line to the line with the PC 5336 m_selected_line = m_pc_line; 5337 m_file_sp = 5338 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file); 5339 if (m_file_sp) { 5340 const size_t num_lines = m_file_sp->GetNumLines(); 5341 m_line_width = 1; 5342 for (size_t n = num_lines; n >= 10; n = n / 10) 5343 ++m_line_width; 5344 5345 if (num_lines < num_visible_lines || 5346 m_selected_line < num_visible_lines) 5347 m_first_visible_line = 0; 5348 else 5349 m_first_visible_line = m_selected_line - 10; 5350 } 5351 } 5352 } else { 5353 m_file_sp.reset(); 5354 } 5355 5356 if (!m_file_sp || m_file_sp->GetNumLines() == 0) { 5357 // Show disassembly 5358 bool prefer_file_cache = false; 5359 if (m_sc.function) { 5360 if (m_disassembly_scope != m_sc.function) { 5361 m_disassembly_scope = m_sc.function; 5362 m_disassembly_sp = m_sc.function->GetInstructions( 5363 exe_ctx, nullptr, !prefer_file_cache); 5364 if (m_disassembly_sp) { 5365 set_selected_line_to_pc = true; 5366 m_disassembly_range = m_sc.function->GetAddressRange(); 5367 } else { 5368 m_disassembly_range.Clear(); 5369 } 5370 } else { 5371 set_selected_line_to_pc = context_changed; 5372 } 5373 } else if (m_sc.symbol) { 5374 if (m_disassembly_scope != m_sc.symbol) { 5375 m_disassembly_scope = m_sc.symbol; 5376 m_disassembly_sp = m_sc.symbol->GetInstructions( 5377 exe_ctx, nullptr, prefer_file_cache); 5378 if (m_disassembly_sp) { 5379 set_selected_line_to_pc = true; 5380 m_disassembly_range.GetBaseAddress() = 5381 m_sc.symbol->GetAddress(); 5382 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize()); 5383 } else { 5384 m_disassembly_range.Clear(); 5385 } 5386 } else { 5387 set_selected_line_to_pc = context_changed; 5388 } 5389 } 5390 } 5391 } else { 5392 m_pc_line = UINT32_MAX; 5393 } 5394 } 5395 5396 const int window_width = window.GetWidth(); 5397 window.Erase(); 5398 window.DrawTitleBox("Sources"); 5399 if (!m_title.GetString().empty()) { 5400 window.AttributeOn(A_REVERSE); 5401 window.MoveCursor(1, 1); 5402 window.PutChar(' '); 5403 window.PutCStringTruncated(1, m_title.GetString().str().c_str()); 5404 int x = window.GetCursorX(); 5405 if (x < window_width - 1) { 5406 window.Printf("%*s", window_width - x - 1, ""); 5407 } 5408 window.AttributeOff(A_REVERSE); 5409 } 5410 5411 Target *target = exe_ctx.GetTargetPtr(); 5412 const size_t num_source_lines = GetNumSourceLines(); 5413 if (num_source_lines > 0) { 5414 // Display source 5415 BreakpointLines bp_lines; 5416 if (target) { 5417 BreakpointList &bp_list = target->GetBreakpointList(); 5418 const size_t num_bps = bp_list.GetSize(); 5419 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 5420 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 5421 const size_t num_bps_locs = bp_sp->GetNumLocations(); 5422 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 5423 BreakpointLocationSP bp_loc_sp = 5424 bp_sp->GetLocationAtIndex(bp_loc_idx); 5425 LineEntry bp_loc_line_entry; 5426 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( 5427 bp_loc_line_entry)) { 5428 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) { 5429 bp_lines.insert(bp_loc_line_entry.line); 5430 } 5431 } 5432 } 5433 } 5434 } 5435 5436 const attr_t selected_highlight_attr = A_REVERSE; 5437 const attr_t pc_highlight_attr = COLOR_PAIR(BlackOnBlue); 5438 5439 for (size_t i = 0; i < num_visible_lines; ++i) { 5440 const uint32_t curr_line = m_first_visible_line + i; 5441 if (curr_line < num_source_lines) { 5442 const int line_y = m_min_y + i; 5443 window.MoveCursor(1, line_y); 5444 const bool is_pc_line = curr_line == m_pc_line; 5445 const bool line_is_selected = m_selected_line == curr_line; 5446 // Highlight the line as the PC line first, then if the selected line 5447 // isn't the same as the PC line, highlight it differently 5448 attr_t highlight_attr = 0; 5449 attr_t bp_attr = 0; 5450 if (is_pc_line) 5451 highlight_attr = pc_highlight_attr; 5452 else if (line_is_selected) 5453 highlight_attr = selected_highlight_attr; 5454 5455 if (bp_lines.find(curr_line + 1) != bp_lines.end()) 5456 bp_attr = COLOR_PAIR(BlackOnWhite); 5457 5458 if (bp_attr) 5459 window.AttributeOn(bp_attr); 5460 5461 window.Printf(" %*u ", m_line_width, curr_line + 1); 5462 5463 if (bp_attr) 5464 window.AttributeOff(bp_attr); 5465 5466 window.PutChar(ACS_VLINE); 5467 // Mark the line with the PC with a diamond 5468 if (is_pc_line) 5469 window.PutChar(ACS_DIAMOND); 5470 else 5471 window.PutChar(' '); 5472 5473 if (highlight_attr) 5474 window.AttributeOn(highlight_attr); 5475 5476 StreamString lineStream; 5477 m_file_sp->DisplaySourceLines(curr_line + 1, {}, 0, 0, &lineStream); 5478 StringRef line = lineStream.GetString(); 5479 if (line.endswith("\n")) 5480 line = line.drop_back(); 5481 bool wasWritten = window.OutputColoredStringTruncated( 5482 1, line, m_first_visible_column, line_is_selected); 5483 if (line_is_selected && !wasWritten) { 5484 // Draw an empty space to show the selected line if empty, 5485 // or draw '<' if nothing is visible because of scrolling too much 5486 // to the right. 5487 window.PutCStringTruncated( 5488 1, line.empty() && m_first_visible_column == 0 ? " " : "<"); 5489 } 5490 5491 if (is_pc_line && frame_sp && 5492 frame_sp->GetConcreteFrameIndex() == 0) { 5493 StopInfoSP stop_info_sp; 5494 if (thread) 5495 stop_info_sp = thread->GetStopInfo(); 5496 if (stop_info_sp) { 5497 const char *stop_description = stop_info_sp->GetDescription(); 5498 if (stop_description && stop_description[0]) { 5499 size_t stop_description_len = strlen(stop_description); 5500 int desc_x = window_width - stop_description_len - 16; 5501 if (desc_x - window.GetCursorX() > 0) 5502 window.Printf("%*s", desc_x - window.GetCursorX(), ""); 5503 window.MoveCursor(window_width - stop_description_len - 16, 5504 line_y); 5505 const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue); 5506 window.AttributeOn(stop_reason_attr); 5507 window.PrintfTruncated(1, " <<< Thread %u: %s ", 5508 thread->GetIndexID(), stop_description); 5509 window.AttributeOff(stop_reason_attr); 5510 } 5511 } else { 5512 window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); 5513 } 5514 } 5515 if (highlight_attr) 5516 window.AttributeOff(highlight_attr); 5517 } else { 5518 break; 5519 } 5520 } 5521 } else { 5522 size_t num_disassembly_lines = GetNumDisassemblyLines(); 5523 if (num_disassembly_lines > 0) { 5524 // Display disassembly 5525 BreakpointAddrs bp_file_addrs; 5526 Target *target = exe_ctx.GetTargetPtr(); 5527 if (target) { 5528 BreakpointList &bp_list = target->GetBreakpointList(); 5529 const size_t num_bps = bp_list.GetSize(); 5530 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 5531 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 5532 const size_t num_bps_locs = bp_sp->GetNumLocations(); 5533 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; 5534 ++bp_loc_idx) { 5535 BreakpointLocationSP bp_loc_sp = 5536 bp_sp->GetLocationAtIndex(bp_loc_idx); 5537 LineEntry bp_loc_line_entry; 5538 const lldb::addr_t file_addr = 5539 bp_loc_sp->GetAddress().GetFileAddress(); 5540 if (file_addr != LLDB_INVALID_ADDRESS) { 5541 if (m_disassembly_range.ContainsFileAddress(file_addr)) 5542 bp_file_addrs.insert(file_addr); 5543 } 5544 } 5545 } 5546 } 5547 5548 const attr_t selected_highlight_attr = A_REVERSE; 5549 const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue); 5550 5551 StreamString strm; 5552 5553 InstructionList &insts = m_disassembly_sp->GetInstructionList(); 5554 Address pc_address; 5555 5556 if (frame_sp) 5557 pc_address = frame_sp->GetFrameCodeAddress(); 5558 const uint32_t pc_idx = 5559 pc_address.IsValid() 5560 ? insts.GetIndexOfInstructionAtAddress(pc_address) 5561 : UINT32_MAX; 5562 if (set_selected_line_to_pc) { 5563 m_selected_line = pc_idx; 5564 } 5565 5566 const uint32_t non_visible_pc_offset = (num_visible_lines / 5); 5567 if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines) 5568 m_first_visible_line = 0; 5569 5570 if (pc_idx < num_disassembly_lines) { 5571 if (pc_idx < static_cast<uint32_t>(m_first_visible_line) || 5572 pc_idx >= m_first_visible_line + num_visible_lines) 5573 m_first_visible_line = pc_idx - non_visible_pc_offset; 5574 } 5575 5576 for (size_t i = 0; i < num_visible_lines; ++i) { 5577 const uint32_t inst_idx = m_first_visible_line + i; 5578 Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get(); 5579 if (!inst) 5580 break; 5581 5582 const int line_y = m_min_y + i; 5583 window.MoveCursor(1, line_y); 5584 const bool is_pc_line = frame_sp && inst_idx == pc_idx; 5585 const bool line_is_selected = m_selected_line == inst_idx; 5586 // Highlight the line as the PC line first, then if the selected line 5587 // isn't the same as the PC line, highlight it differently 5588 attr_t highlight_attr = 0; 5589 attr_t bp_attr = 0; 5590 if (is_pc_line) 5591 highlight_attr = pc_highlight_attr; 5592 else if (line_is_selected) 5593 highlight_attr = selected_highlight_attr; 5594 5595 if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) != 5596 bp_file_addrs.end()) 5597 bp_attr = COLOR_PAIR(BlackOnWhite); 5598 5599 if (bp_attr) 5600 window.AttributeOn(bp_attr); 5601 5602 window.Printf(" 0x%16.16llx ", 5603 static_cast<unsigned long long>( 5604 inst->GetAddress().GetLoadAddress(target))); 5605 5606 if (bp_attr) 5607 window.AttributeOff(bp_attr); 5608 5609 window.PutChar(ACS_VLINE); 5610 // Mark the line with the PC with a diamond 5611 if (is_pc_line) 5612 window.PutChar(ACS_DIAMOND); 5613 else 5614 window.PutChar(' '); 5615 5616 if (highlight_attr) 5617 window.AttributeOn(highlight_attr); 5618 5619 const char *mnemonic = inst->GetMnemonic(&exe_ctx); 5620 const char *operands = inst->GetOperands(&exe_ctx); 5621 const char *comment = inst->GetComment(&exe_ctx); 5622 5623 if (mnemonic != nullptr && mnemonic[0] == '\0') 5624 mnemonic = nullptr; 5625 if (operands != nullptr && operands[0] == '\0') 5626 operands = nullptr; 5627 if (comment != nullptr && comment[0] == '\0') 5628 comment = nullptr; 5629 5630 strm.Clear(); 5631 5632 if (mnemonic != nullptr && operands != nullptr && comment != nullptr) 5633 strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment); 5634 else if (mnemonic != nullptr && operands != nullptr) 5635 strm.Printf("%-8s %s", mnemonic, operands); 5636 else if (mnemonic != nullptr) 5637 strm.Printf("%s", mnemonic); 5638 5639 int right_pad = 1; 5640 window.PutCStringTruncated( 5641 right_pad, 5642 strm.GetString().substr(m_first_visible_column).data()); 5643 5644 if (is_pc_line && frame_sp && 5645 frame_sp->GetConcreteFrameIndex() == 0) { 5646 StopInfoSP stop_info_sp; 5647 if (thread) 5648 stop_info_sp = thread->GetStopInfo(); 5649 if (stop_info_sp) { 5650 const char *stop_description = stop_info_sp->GetDescription(); 5651 if (stop_description && stop_description[0]) { 5652 size_t stop_description_len = strlen(stop_description); 5653 int desc_x = window_width - stop_description_len - 16; 5654 if (desc_x - window.GetCursorX() > 0) 5655 window.Printf("%*s", desc_x - window.GetCursorX(), ""); 5656 window.MoveCursor(window_width - stop_description_len - 15, 5657 line_y); 5658 window.PrintfTruncated(1, "<<< Thread %u: %s ", 5659 thread->GetIndexID(), stop_description); 5660 } 5661 } else { 5662 window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); 5663 } 5664 } 5665 if (highlight_attr) 5666 window.AttributeOff(highlight_attr); 5667 } 5668 } 5669 } 5670 return true; // Drawing handled 5671 } 5672 5673 size_t GetNumLines() { 5674 size_t num_lines = GetNumSourceLines(); 5675 if (num_lines == 0) 5676 num_lines = GetNumDisassemblyLines(); 5677 return num_lines; 5678 } 5679 5680 size_t GetNumSourceLines() const { 5681 if (m_file_sp) 5682 return m_file_sp->GetNumLines(); 5683 return 0; 5684 } 5685 5686 size_t GetNumDisassemblyLines() const { 5687 if (m_disassembly_sp) 5688 return m_disassembly_sp->GetInstructionList().GetSize(); 5689 return 0; 5690 } 5691 5692 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 5693 const uint32_t num_visible_lines = NumVisibleLines(); 5694 const size_t num_lines = GetNumLines(); 5695 5696 switch (c) { 5697 case ',': 5698 case KEY_PPAGE: 5699 // Page up key 5700 if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines) 5701 m_first_visible_line -= num_visible_lines; 5702 else 5703 m_first_visible_line = 0; 5704 m_selected_line = m_first_visible_line; 5705 return eKeyHandled; 5706 5707 case '.': 5708 case KEY_NPAGE: 5709 // Page down key 5710 { 5711 if (m_first_visible_line + num_visible_lines < num_lines) 5712 m_first_visible_line += num_visible_lines; 5713 else if (num_lines < num_visible_lines) 5714 m_first_visible_line = 0; 5715 else 5716 m_first_visible_line = num_lines - num_visible_lines; 5717 m_selected_line = m_first_visible_line; 5718 } 5719 return eKeyHandled; 5720 5721 case KEY_UP: 5722 if (m_selected_line > 0) { 5723 m_selected_line--; 5724 if (static_cast<size_t>(m_first_visible_line) > m_selected_line) 5725 m_first_visible_line = m_selected_line; 5726 } 5727 return eKeyHandled; 5728 5729 case KEY_DOWN: 5730 if (m_selected_line + 1 < num_lines) { 5731 m_selected_line++; 5732 if (m_first_visible_line + num_visible_lines < m_selected_line) 5733 m_first_visible_line++; 5734 } 5735 return eKeyHandled; 5736 5737 case KEY_LEFT: 5738 if (m_first_visible_column > 0) 5739 --m_first_visible_column; 5740 return eKeyHandled; 5741 5742 case KEY_RIGHT: 5743 ++m_first_visible_column; 5744 return eKeyHandled; 5745 5746 case '\r': 5747 case '\n': 5748 case KEY_ENTER: 5749 // Set a breakpoint and run to the line using a one shot breakpoint 5750 if (GetNumSourceLines() > 0) { 5751 ExecutionContext exe_ctx = 5752 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5753 if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) { 5754 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 5755 nullptr, // Don't limit the breakpoint to certain modules 5756 m_file_sp->GetFileSpec(), // Source file 5757 m_selected_line + 5758 1, // Source line number (m_selected_line is zero based) 5759 0, // Unspecified column. 5760 0, // No offset 5761 eLazyBoolCalculate, // Check inlines using global setting 5762 eLazyBoolCalculate, // Skip prologue using global setting, 5763 false, // internal 5764 false, // request_hardware 5765 eLazyBoolCalculate); // move_to_nearest_code 5766 // Make breakpoint one shot 5767 bp_sp->GetOptions().SetOneShot(true); 5768 exe_ctx.GetProcessRef().Resume(); 5769 } 5770 } else if (m_selected_line < GetNumDisassemblyLines()) { 5771 const Instruction *inst = m_disassembly_sp->GetInstructionList() 5772 .GetInstructionAtIndex(m_selected_line) 5773 .get(); 5774 ExecutionContext exe_ctx = 5775 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5776 if (exe_ctx.HasTargetScope()) { 5777 Address addr = inst->GetAddress(); 5778 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 5779 addr, // lldb_private::Address 5780 false, // internal 5781 false); // request_hardware 5782 // Make breakpoint one shot 5783 bp_sp->GetOptions().SetOneShot(true); 5784 exe_ctx.GetProcessRef().Resume(); 5785 } 5786 } 5787 return eKeyHandled; 5788 5789 case 'b': // 'b' == toggle breakpoint on currently selected line 5790 ToggleBreakpointOnSelectedLine(); 5791 return eKeyHandled; 5792 5793 case 'D': // 'D' == detach and keep stopped 5794 { 5795 ExecutionContext exe_ctx = 5796 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5797 if (exe_ctx.HasProcessScope()) 5798 exe_ctx.GetProcessRef().Detach(true); 5799 } 5800 return eKeyHandled; 5801 5802 case 'c': 5803 // 'c' == continue 5804 { 5805 ExecutionContext exe_ctx = 5806 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5807 if (exe_ctx.HasProcessScope()) 5808 exe_ctx.GetProcessRef().Resume(); 5809 } 5810 return eKeyHandled; 5811 5812 case 'f': 5813 // 'f' == step out (finish) 5814 { 5815 ExecutionContext exe_ctx = 5816 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5817 if (exe_ctx.HasThreadScope() && 5818 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 5819 exe_ctx.GetThreadRef().StepOut(); 5820 } 5821 } 5822 return eKeyHandled; 5823 5824 case 'n': // 'n' == step over 5825 case 'N': // 'N' == step over instruction 5826 { 5827 ExecutionContext exe_ctx = 5828 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5829 if (exe_ctx.HasThreadScope() && 5830 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 5831 bool source_step = (c == 'n'); 5832 exe_ctx.GetThreadRef().StepOver(source_step); 5833 } 5834 } 5835 return eKeyHandled; 5836 5837 case 's': // 's' == step into 5838 case 'S': // 'S' == step into instruction 5839 { 5840 ExecutionContext exe_ctx = 5841 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5842 if (exe_ctx.HasThreadScope() && 5843 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 5844 bool source_step = (c == 's'); 5845 exe_ctx.GetThreadRef().StepIn(source_step); 5846 } 5847 } 5848 return eKeyHandled; 5849 5850 case 'u': // 'u' == frame up 5851 case 'd': // 'd' == frame down 5852 { 5853 ExecutionContext exe_ctx = 5854 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5855 if (exe_ctx.HasThreadScope()) { 5856 Thread *thread = exe_ctx.GetThreadPtr(); 5857 uint32_t frame_idx = thread->GetSelectedFrameIndex(); 5858 if (frame_idx == UINT32_MAX) 5859 frame_idx = 0; 5860 if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount()) 5861 ++frame_idx; 5862 else if (c == 'd' && frame_idx > 0) 5863 --frame_idx; 5864 if (thread->SetSelectedFrameByIndex(frame_idx, true)) 5865 exe_ctx.SetFrameSP(thread->GetSelectedFrame()); 5866 } 5867 } 5868 return eKeyHandled; 5869 5870 case 'h': 5871 window.CreateHelpSubwindow(); 5872 return eKeyHandled; 5873 5874 default: 5875 break; 5876 } 5877 return eKeyNotHandled; 5878 } 5879 5880 void ToggleBreakpointOnSelectedLine() { 5881 ExecutionContext exe_ctx = 5882 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5883 if (!exe_ctx.HasTargetScope()) 5884 return; 5885 if (GetNumSourceLines() > 0) { 5886 // Source file breakpoint. 5887 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); 5888 const size_t num_bps = bp_list.GetSize(); 5889 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 5890 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 5891 const size_t num_bps_locs = bp_sp->GetNumLocations(); 5892 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 5893 BreakpointLocationSP bp_loc_sp = 5894 bp_sp->GetLocationAtIndex(bp_loc_idx); 5895 LineEntry bp_loc_line_entry; 5896 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( 5897 bp_loc_line_entry)) { 5898 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file && 5899 m_selected_line + 1 == bp_loc_line_entry.line) { 5900 bool removed = 5901 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); 5902 assert(removed); 5903 UNUSED_IF_ASSERT_DISABLED(removed); 5904 return; // Existing breakpoint removed. 5905 } 5906 } 5907 } 5908 } 5909 // No breakpoint found on the location, add it. 5910 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 5911 nullptr, // Don't limit the breakpoint to certain modules 5912 m_file_sp->GetFileSpec(), // Source file 5913 m_selected_line + 5914 1, // Source line number (m_selected_line is zero based) 5915 0, // No column specified. 5916 0, // No offset 5917 eLazyBoolCalculate, // Check inlines using global setting 5918 eLazyBoolCalculate, // Skip prologue using global setting, 5919 false, // internal 5920 false, // request_hardware 5921 eLazyBoolCalculate); // move_to_nearest_code 5922 } else { 5923 // Disassembly breakpoint. 5924 assert(GetNumDisassemblyLines() > 0); 5925 assert(m_selected_line < GetNumDisassemblyLines()); 5926 const Instruction *inst = m_disassembly_sp->GetInstructionList() 5927 .GetInstructionAtIndex(m_selected_line) 5928 .get(); 5929 Address addr = inst->GetAddress(); 5930 // Try to find it. 5931 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); 5932 const size_t num_bps = bp_list.GetSize(); 5933 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 5934 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 5935 const size_t num_bps_locs = bp_sp->GetNumLocations(); 5936 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 5937 BreakpointLocationSP bp_loc_sp = 5938 bp_sp->GetLocationAtIndex(bp_loc_idx); 5939 LineEntry bp_loc_line_entry; 5940 const lldb::addr_t file_addr = 5941 bp_loc_sp->GetAddress().GetFileAddress(); 5942 if (file_addr == addr.GetFileAddress()) { 5943 bool removed = 5944 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); 5945 assert(removed); 5946 UNUSED_IF_ASSERT_DISABLED(removed); 5947 return; // Existing breakpoint removed. 5948 } 5949 } 5950 } 5951 // No breakpoint found on the address, add it. 5952 BreakpointSP bp_sp = 5953 exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address 5954 false, // internal 5955 false); // request_hardware 5956 } 5957 } 5958 5959 protected: 5960 typedef std::set<uint32_t> BreakpointLines; 5961 typedef std::set<lldb::addr_t> BreakpointAddrs; 5962 5963 Debugger &m_debugger; 5964 SymbolContext m_sc; 5965 SourceManager::FileSP m_file_sp; 5966 SymbolContextScope *m_disassembly_scope; 5967 lldb::DisassemblerSP m_disassembly_sp; 5968 AddressRange m_disassembly_range; 5969 StreamString m_title; 5970 lldb::user_id_t m_tid; 5971 int m_line_width; 5972 uint32_t m_selected_line; // The selected line 5973 uint32_t m_pc_line; // The line with the PC 5974 uint32_t m_stop_id; 5975 uint32_t m_frame_idx; 5976 int m_first_visible_line; 5977 int m_first_visible_column; 5978 int m_min_x; 5979 int m_min_y; 5980 int m_max_x; 5981 int m_max_y; 5982 }; 5983 5984 DisplayOptions ValueObjectListDelegate::g_options = {true}; 5985 5986 IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger) 5987 : IOHandler(debugger, IOHandler::Type::Curses) {} 5988 5989 void IOHandlerCursesGUI::Activate() { 5990 IOHandler::Activate(); 5991 if (!m_app_ap) { 5992 m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE()); 5993 5994 // This is both a window and a menu delegate 5995 std::shared_ptr<ApplicationDelegate> app_delegate_sp( 5996 new ApplicationDelegate(*m_app_ap, m_debugger)); 5997 5998 MenuDelegateSP app_menu_delegate_sp = 5999 std::static_pointer_cast<MenuDelegate>(app_delegate_sp); 6000 MenuSP lldb_menu_sp( 6001 new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB)); 6002 MenuSP exit_menuitem_sp( 6003 new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit)); 6004 exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit); 6005 lldb_menu_sp->AddSubmenu(MenuSP(new Menu( 6006 "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout))); 6007 lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 6008 lldb_menu_sp->AddSubmenu(exit_menuitem_sp); 6009 6010 MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2), 6011 ApplicationDelegate::eMenuID_Target)); 6012 target_menu_sp->AddSubmenu(MenuSP(new Menu( 6013 "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate))); 6014 target_menu_sp->AddSubmenu(MenuSP(new Menu( 6015 "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete))); 6016 6017 MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3), 6018 ApplicationDelegate::eMenuID_Process)); 6019 process_menu_sp->AddSubmenu(MenuSP(new Menu( 6020 "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach))); 6021 process_menu_sp->AddSubmenu( 6022 MenuSP(new Menu("Detach and resume", nullptr, 'd', 6023 ApplicationDelegate::eMenuID_ProcessDetachResume))); 6024 process_menu_sp->AddSubmenu( 6025 MenuSP(new Menu("Detach suspended", nullptr, 's', 6026 ApplicationDelegate::eMenuID_ProcessDetachSuspended))); 6027 process_menu_sp->AddSubmenu(MenuSP(new Menu( 6028 "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch))); 6029 process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 6030 process_menu_sp->AddSubmenu( 6031 MenuSP(new Menu("Continue", nullptr, 'c', 6032 ApplicationDelegate::eMenuID_ProcessContinue))); 6033 process_menu_sp->AddSubmenu(MenuSP(new Menu( 6034 "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt))); 6035 process_menu_sp->AddSubmenu(MenuSP(new Menu( 6036 "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill))); 6037 6038 MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4), 6039 ApplicationDelegate::eMenuID_Thread)); 6040 thread_menu_sp->AddSubmenu(MenuSP(new Menu( 6041 "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn))); 6042 thread_menu_sp->AddSubmenu( 6043 MenuSP(new Menu("Step Over", nullptr, 'v', 6044 ApplicationDelegate::eMenuID_ThreadStepOver))); 6045 thread_menu_sp->AddSubmenu(MenuSP(new Menu( 6046 "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut))); 6047 6048 MenuSP view_menu_sp( 6049 new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View)); 6050 view_menu_sp->AddSubmenu( 6051 MenuSP(new Menu("Backtrace", nullptr, 'b', 6052 ApplicationDelegate::eMenuID_ViewBacktrace))); 6053 view_menu_sp->AddSubmenu( 6054 MenuSP(new Menu("Registers", nullptr, 'r', 6055 ApplicationDelegate::eMenuID_ViewRegisters))); 6056 view_menu_sp->AddSubmenu(MenuSP(new Menu( 6057 "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource))); 6058 view_menu_sp->AddSubmenu( 6059 MenuSP(new Menu("Variables", nullptr, 'v', 6060 ApplicationDelegate::eMenuID_ViewVariables))); 6061 6062 MenuSP help_menu_sp( 6063 new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help)); 6064 help_menu_sp->AddSubmenu(MenuSP(new Menu( 6065 "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp))); 6066 6067 m_app_ap->Initialize(); 6068 WindowSP &main_window_sp = m_app_ap->GetMainWindow(); 6069 6070 MenuSP menubar_sp(new Menu(Menu::Type::Bar)); 6071 menubar_sp->AddSubmenu(lldb_menu_sp); 6072 menubar_sp->AddSubmenu(target_menu_sp); 6073 menubar_sp->AddSubmenu(process_menu_sp); 6074 menubar_sp->AddSubmenu(thread_menu_sp); 6075 menubar_sp->AddSubmenu(view_menu_sp); 6076 menubar_sp->AddSubmenu(help_menu_sp); 6077 menubar_sp->SetDelegate(app_menu_delegate_sp); 6078 6079 Rect content_bounds = main_window_sp->GetFrame(); 6080 Rect menubar_bounds = content_bounds.MakeMenuBar(); 6081 Rect status_bounds = content_bounds.MakeStatusBar(); 6082 Rect source_bounds; 6083 Rect variables_bounds; 6084 Rect threads_bounds; 6085 Rect source_variables_bounds; 6086 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, 6087 threads_bounds); 6088 source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds, 6089 variables_bounds); 6090 6091 WindowSP menubar_window_sp = 6092 main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false); 6093 // Let the menubar get keys if the active window doesn't handle the keys 6094 // that are typed so it can respond to menubar key presses. 6095 menubar_window_sp->SetCanBeActive( 6096 false); // Don't let the menubar become the active window 6097 menubar_window_sp->SetDelegate(menubar_sp); 6098 6099 WindowSP source_window_sp( 6100 main_window_sp->CreateSubWindow("Source", source_bounds, true)); 6101 WindowSP variables_window_sp( 6102 main_window_sp->CreateSubWindow("Variables", variables_bounds, false)); 6103 WindowSP threads_window_sp( 6104 main_window_sp->CreateSubWindow("Threads", threads_bounds, false)); 6105 WindowSP status_window_sp( 6106 main_window_sp->CreateSubWindow("Status", status_bounds, false)); 6107 status_window_sp->SetCanBeActive( 6108 false); // Don't let the status bar become the active window 6109 main_window_sp->SetDelegate( 6110 std::static_pointer_cast<WindowDelegate>(app_delegate_sp)); 6111 source_window_sp->SetDelegate( 6112 WindowDelegateSP(new SourceFileWindowDelegate(m_debugger))); 6113 variables_window_sp->SetDelegate( 6114 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 6115 TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger)); 6116 threads_window_sp->SetDelegate(WindowDelegateSP( 6117 new TreeWindowDelegate(m_debugger, thread_delegate_sp))); 6118 status_window_sp->SetDelegate( 6119 WindowDelegateSP(new StatusBarWindowDelegate(m_debugger))); 6120 6121 // Show the main help window once the first time the curses GUI is launched 6122 static bool g_showed_help = false; 6123 if (!g_showed_help) { 6124 g_showed_help = true; 6125 main_window_sp->CreateHelpSubwindow(); 6126 } 6127 6128 // All colors with black background. 6129 init_pair(1, COLOR_BLACK, COLOR_BLACK); 6130 init_pair(2, COLOR_RED, COLOR_BLACK); 6131 init_pair(3, COLOR_GREEN, COLOR_BLACK); 6132 init_pair(4, COLOR_YELLOW, COLOR_BLACK); 6133 init_pair(5, COLOR_BLUE, COLOR_BLACK); 6134 init_pair(6, COLOR_MAGENTA, COLOR_BLACK); 6135 init_pair(7, COLOR_CYAN, COLOR_BLACK); 6136 init_pair(8, COLOR_WHITE, COLOR_BLACK); 6137 // All colors with blue background. 6138 init_pair(9, COLOR_BLACK, COLOR_BLUE); 6139 init_pair(10, COLOR_RED, COLOR_BLUE); 6140 init_pair(11, COLOR_GREEN, COLOR_BLUE); 6141 init_pair(12, COLOR_YELLOW, COLOR_BLUE); 6142 init_pair(13, COLOR_BLUE, COLOR_BLUE); 6143 init_pair(14, COLOR_MAGENTA, COLOR_BLUE); 6144 init_pair(15, COLOR_CYAN, COLOR_BLUE); 6145 init_pair(16, COLOR_WHITE, COLOR_BLUE); 6146 // These must match the order in the color indexes enum. 6147 init_pair(17, COLOR_BLACK, COLOR_WHITE); 6148 init_pair(18, COLOR_MAGENTA, COLOR_WHITE); 6149 static_assert(LastColorPairIndex == 18, "Color indexes do not match."); 6150 6151 define_key("\033[Z", KEY_SHIFT_TAB); 6152 } 6153 } 6154 6155 void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); } 6156 6157 void IOHandlerCursesGUI::Run() { 6158 m_app_ap->Run(m_debugger); 6159 SetIsDone(true); 6160 } 6161 6162 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default; 6163 6164 void IOHandlerCursesGUI::Cancel() {} 6165 6166 bool IOHandlerCursesGUI::Interrupt() { return false; } 6167 6168 void IOHandlerCursesGUI::GotEOF() {} 6169 6170 void IOHandlerCursesGUI::TerminalSizeChanged() { 6171 m_app_ap->TerminalSizeChanged(); 6172 } 6173 6174 #endif // LLDB_ENABLE_CURSES 6175