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