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