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