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 // If the last element of the field is selected and it didn't handle the key. 1861 // Select the next field or new button if the selected field is the last one. 1862 HandleCharResult SelectNextInList(int key) { 1863 assert(m_selection_type == SelectionType::Field); 1864 1865 FieldDelegate &field = m_fields[m_selection_index]; 1866 if (field.FieldDelegateHandleChar(key) == eKeyHandled) 1867 return eKeyHandled; 1868 1869 if (!field.FieldDelegateOnLastOrOnlyElement()) 1870 return eKeyNotHandled; 1871 1872 field.FieldDelegateExitCallback(); 1873 1874 if (m_selection_index == GetNumberOfFields() - 1) { 1875 m_selection_type = SelectionType::NewButton; 1876 return eKeyHandled; 1877 } 1878 1879 m_selection_index++; 1880 FieldDelegate &next_field = m_fields[m_selection_index]; 1881 next_field.FieldDelegateSelectFirstElement(); 1882 return eKeyHandled; 1883 } 1884 1885 HandleCharResult FieldDelegateHandleChar(int key) override { 1886 switch (key) { 1887 case '\r': 1888 case '\n': 1889 case KEY_ENTER: 1890 switch (m_selection_type) { 1891 case SelectionType::NewButton: 1892 AddNewField(); 1893 return eKeyHandled; 1894 case SelectionType::RemoveButton: 1895 RemoveField(); 1896 return eKeyHandled; 1897 case SelectionType::Field: 1898 return SelectNextInList(key); 1899 } 1900 break; 1901 case '\t': 1902 return SelectNext(key); 1903 case KEY_SHIFT_TAB: 1904 return SelectPrevious(key); 1905 default: 1906 break; 1907 } 1908 1909 // If the key wasn't handled and one of the fields is selected, pass the key 1910 // to that field. 1911 if (m_selection_type == SelectionType::Field) { 1912 return m_fields[m_selection_index].FieldDelegateHandleChar(key); 1913 } 1914 1915 return eKeyNotHandled; 1916 } 1917 1918 bool FieldDelegateOnLastOrOnlyElement() override { 1919 if (m_selection_type == SelectionType::NewButton) { 1920 return true; 1921 } 1922 return false; 1923 } 1924 1925 bool FieldDelegateOnFirstOrOnlyElement() override { 1926 if (m_selection_type == SelectionType::NewButton && 1927 GetNumberOfFields() == 0) 1928 return true; 1929 1930 if (m_selection_type == SelectionType::Field && m_selection_index == 0) { 1931 FieldDelegate &field = m_fields[m_selection_index]; 1932 return field.FieldDelegateOnFirstOrOnlyElement(); 1933 } 1934 1935 return false; 1936 } 1937 1938 void FieldDelegateSelectFirstElement() override { 1939 if (GetNumberOfFields() == 0) { 1940 m_selection_type = SelectionType::NewButton; 1941 return; 1942 } 1943 1944 m_selection_type = SelectionType::Field; 1945 m_selection_index = 0; 1946 } 1947 1948 void FieldDelegateSelectLastElement() override { 1949 m_selection_type = SelectionType::NewButton; 1950 return; 1951 } 1952 1953 int GetNumberOfFields() { return m_fields.size(); } 1954 1955 // Returns the form delegate at the current index. 1956 T &GetField(int index) { return m_fields[index]; } 1957 1958 protected: 1959 std::string m_label; 1960 // The default field delegate instance from which new field delegates will be 1961 // created though a copy. 1962 T m_default_field; 1963 std::vector<T> m_fields; 1964 int m_selection_index; 1965 // See SelectionType class enum. 1966 SelectionType m_selection_type; 1967 }; 1968 1969 class ArgumentsFieldDelegate : public ListFieldDelegate<TextFieldDelegate> { 1970 public: 1971 ArgumentsFieldDelegate() 1972 : ListFieldDelegate("Arguments", 1973 TextFieldDelegate("Argument", "", false)) {} 1974 1975 Args GetArguments() { 1976 Args arguments; 1977 for (int i = 0; i < GetNumberOfFields(); i++) { 1978 arguments.AppendArgument(GetField(i).GetText()); 1979 } 1980 return arguments; 1981 } 1982 1983 void AddArguments(const Args &arguments) { 1984 for (size_t i = 0; i < arguments.GetArgumentCount(); i++) { 1985 AddNewField(); 1986 TextFieldDelegate &field = GetField(GetNumberOfFields() - 1); 1987 field.SetText(arguments.GetArgumentAtIndex(i)); 1988 } 1989 } 1990 }; 1991 1992 template <class KeyFieldDelegateType, class ValueFieldDelegateType> 1993 class MappingFieldDelegate : public FieldDelegate { 1994 public: 1995 MappingFieldDelegate(KeyFieldDelegateType key_field, 1996 ValueFieldDelegateType value_field) 1997 : m_key_field(key_field), m_value_field(value_field), 1998 m_selection_type(SelectionType::Key) {} 1999 2000 // Signify which element is selected. The key field or its value field. 2001 enum class SelectionType { Key, Value }; 2002 2003 // A mapping field is drawn as two text fields with a right arrow in between. 2004 // The first field stores the key of the mapping and the second stores the 2005 // value if the mapping. 2006 // 2007 // __[Key]_____________ __[Value]___________ 2008 // | | > | | 2009 // |__________________| |__________________| 2010 // - Error message if it exists. 2011 2012 // The mapping field has a height that is equal to the maximum height between 2013 // the key and value fields. 2014 int FieldDelegateGetHeight() override { 2015 return std::max(m_key_field.FieldDelegateGetHeight(), 2016 m_value_field.FieldDelegateGetHeight()); 2017 } 2018 2019 void DrawArrow(Surface &surface) { 2020 surface.MoveCursor(0, 1); 2021 surface.PutChar(ACS_RARROW); 2022 } 2023 2024 void FieldDelegateDraw(Surface &surface, bool is_selected) override { 2025 Rect bounds = surface.GetFrame(); 2026 Rect key_field_bounds, arrow_and_value_field_bounds; 2027 bounds.VerticalSplit(bounds.size.width / 2, key_field_bounds, 2028 arrow_and_value_field_bounds); 2029 Rect arrow_bounds, value_field_bounds; 2030 arrow_and_value_field_bounds.VerticalSplit(1, arrow_bounds, 2031 value_field_bounds); 2032 2033 Surface key_field_surface = surface.SubSurface(key_field_bounds); 2034 Surface arrow_surface = surface.SubSurface(arrow_bounds); 2035 Surface value_field_surface = surface.SubSurface(value_field_bounds); 2036 2037 bool key_is_selected = 2038 m_selection_type == SelectionType::Key && is_selected; 2039 m_key_field.FieldDelegateDraw(key_field_surface, key_is_selected); 2040 DrawArrow(arrow_surface); 2041 bool value_is_selected = 2042 m_selection_type == SelectionType::Value && is_selected; 2043 m_value_field.FieldDelegateDraw(value_field_surface, value_is_selected); 2044 } 2045 2046 HandleCharResult SelectNext(int key) { 2047 if (FieldDelegateOnLastOrOnlyElement()) 2048 return eKeyNotHandled; 2049 2050 if (!m_key_field.FieldDelegateOnLastOrOnlyElement()) { 2051 return m_key_field.FieldDelegateHandleChar(key); 2052 } 2053 2054 m_key_field.FieldDelegateExitCallback(); 2055 m_selection_type = SelectionType::Value; 2056 m_value_field.FieldDelegateSelectFirstElement(); 2057 return eKeyHandled; 2058 } 2059 2060 HandleCharResult SelectPrevious(int key) { 2061 if (FieldDelegateOnFirstOrOnlyElement()) 2062 return eKeyNotHandled; 2063 2064 if (!m_value_field.FieldDelegateOnFirstOrOnlyElement()) { 2065 return m_value_field.FieldDelegateHandleChar(key); 2066 } 2067 2068 m_value_field.FieldDelegateExitCallback(); 2069 m_selection_type = SelectionType::Key; 2070 m_key_field.FieldDelegateSelectLastElement(); 2071 return eKeyHandled; 2072 } 2073 2074 // If the value field is selected, pass the key to it. If the key field is 2075 // selected, its last element is selected, and it didn't handle the key, then 2076 // select its corresponding value field. 2077 HandleCharResult SelectNextField(int key) { 2078 if (m_selection_type == SelectionType::Value) { 2079 return m_value_field.FieldDelegateHandleChar(key); 2080 } 2081 2082 if (m_key_field.FieldDelegateHandleChar(key) == eKeyHandled) 2083 return eKeyHandled; 2084 2085 if (!m_key_field.FieldDelegateOnLastOrOnlyElement()) 2086 return eKeyNotHandled; 2087 2088 m_key_field.FieldDelegateExitCallback(); 2089 m_selection_type = SelectionType::Value; 2090 m_value_field.FieldDelegateSelectFirstElement(); 2091 return eKeyHandled; 2092 } 2093 2094 HandleCharResult FieldDelegateHandleChar(int key) override { 2095 switch (key) { 2096 case KEY_RETURN: 2097 return SelectNextField(key); 2098 case '\t': 2099 return SelectNext(key); 2100 case KEY_SHIFT_TAB: 2101 return SelectPrevious(key); 2102 default: 2103 break; 2104 } 2105 2106 // If the key wasn't handled, pass the key to the selected field. 2107 if (m_selection_type == SelectionType::Key) 2108 return m_key_field.FieldDelegateHandleChar(key); 2109 else 2110 return m_value_field.FieldDelegateHandleChar(key); 2111 2112 return eKeyNotHandled; 2113 } 2114 2115 bool FieldDelegateOnFirstOrOnlyElement() override { 2116 return m_selection_type == SelectionType::Key; 2117 } 2118 2119 bool FieldDelegateOnLastOrOnlyElement() override { 2120 return m_selection_type == SelectionType::Value; 2121 } 2122 2123 void FieldDelegateSelectFirstElement() override { 2124 m_selection_type = SelectionType::Key; 2125 } 2126 2127 void FieldDelegateSelectLastElement() override { 2128 m_selection_type = SelectionType::Value; 2129 } 2130 2131 bool FieldDelegateHasError() override { 2132 return m_key_field.FieldDelegateHasError() || 2133 m_value_field.FieldDelegateHasError(); 2134 } 2135 2136 KeyFieldDelegateType &GetKeyField() { return m_key_field; } 2137 2138 ValueFieldDelegateType &GetValueField() { return m_value_field; } 2139 2140 protected: 2141 KeyFieldDelegateType m_key_field; 2142 ValueFieldDelegateType m_value_field; 2143 // See SelectionType class enum. 2144 SelectionType m_selection_type; 2145 }; 2146 2147 class EnvironmentVariableNameFieldDelegate : public TextFieldDelegate { 2148 public: 2149 EnvironmentVariableNameFieldDelegate(const char *content) 2150 : TextFieldDelegate("Name", content, true) {} 2151 2152 // Environment variable names can't contain an equal sign. 2153 bool IsAcceptableChar(int key) override { 2154 return TextFieldDelegate::IsAcceptableChar(key) && key != '='; 2155 } 2156 2157 const std::string &GetName() { return m_content; } 2158 }; 2159 2160 class EnvironmentVariableFieldDelegate 2161 : public MappingFieldDelegate<EnvironmentVariableNameFieldDelegate, 2162 TextFieldDelegate> { 2163 public: 2164 EnvironmentVariableFieldDelegate() 2165 : MappingFieldDelegate( 2166 EnvironmentVariableNameFieldDelegate(""), 2167 TextFieldDelegate("Value", "", /*required=*/false)) {} 2168 2169 const std::string &GetName() { return GetKeyField().GetName(); } 2170 2171 const std::string &GetValue() { return GetValueField().GetText(); } 2172 2173 void SetName(const char *name) { return GetKeyField().SetText(name); } 2174 2175 void SetValue(const char *value) { return GetValueField().SetText(value); } 2176 }; 2177 2178 class EnvironmentVariableListFieldDelegate 2179 : public ListFieldDelegate<EnvironmentVariableFieldDelegate> { 2180 public: 2181 EnvironmentVariableListFieldDelegate(const char *label) 2182 : ListFieldDelegate(label, EnvironmentVariableFieldDelegate()) {} 2183 2184 Environment GetEnvironment() { 2185 Environment environment; 2186 for (int i = 0; i < GetNumberOfFields(); i++) { 2187 environment.insert( 2188 std::make_pair(GetField(i).GetName(), GetField(i).GetValue())); 2189 } 2190 return environment; 2191 } 2192 2193 void AddEnvironmentVariables(const Environment &environment) { 2194 for (auto &variable : environment) { 2195 AddNewField(); 2196 EnvironmentVariableFieldDelegate &field = 2197 GetField(GetNumberOfFields() - 1); 2198 field.SetName(variable.getKey().str().c_str()); 2199 field.SetValue(variable.getValue().c_str()); 2200 } 2201 } 2202 }; 2203 2204 class FormAction { 2205 public: 2206 FormAction(const char *label, std::function<void(Window &)> action) 2207 : m_action(action) { 2208 if (label) 2209 m_label = label; 2210 } 2211 2212 // Draw a centered [Label]. 2213 void Draw(Surface &surface, bool is_selected) { 2214 int x = (surface.GetWidth() - m_label.length()) / 2; 2215 surface.MoveCursor(x, 0); 2216 if (is_selected) 2217 surface.AttributeOn(A_REVERSE); 2218 surface.PutChar('['); 2219 surface.PutCString(m_label.c_str()); 2220 surface.PutChar(']'); 2221 if (is_selected) 2222 surface.AttributeOff(A_REVERSE); 2223 } 2224 2225 void Execute(Window &window) { m_action(window); } 2226 2227 const std::string &GetLabel() { return m_label; } 2228 2229 protected: 2230 std::string m_label; 2231 std::function<void(Window &)> m_action; 2232 }; 2233 2234 class FormDelegate { 2235 public: 2236 FormDelegate() {} 2237 2238 virtual ~FormDelegate() = default; 2239 2240 virtual std::string GetName() = 0; 2241 2242 virtual void UpdateFieldsVisibility() { return; } 2243 2244 FieldDelegate *GetField(uint32_t field_index) { 2245 if (field_index < m_fields.size()) 2246 return m_fields[field_index].get(); 2247 return nullptr; 2248 } 2249 2250 FormAction &GetAction(int action_index) { return m_actions[action_index]; } 2251 2252 int GetNumberOfFields() { return m_fields.size(); } 2253 2254 int GetNumberOfActions() { return m_actions.size(); } 2255 2256 bool HasError() { return !m_error.empty(); } 2257 2258 void ClearError() { m_error.clear(); } 2259 2260 const std::string &GetError() { return m_error; } 2261 2262 void SetError(const char *error) { m_error = error; } 2263 2264 // If all fields are valid, true is returned. Otherwise, an error message is 2265 // set and false is returned. This method is usually called at the start of an 2266 // action that requires valid fields. 2267 bool CheckFieldsValidity() { 2268 for (int i = 0; i < GetNumberOfFields(); i++) { 2269 if (GetField(i)->FieldDelegateHasError()) { 2270 SetError("Some fields are invalid!"); 2271 return false; 2272 } 2273 } 2274 return true; 2275 } 2276 2277 // Factory methods to create and add fields of specific types. 2278 2279 TextFieldDelegate *AddTextField(const char *label, const char *content, 2280 bool required) { 2281 TextFieldDelegate *delegate = 2282 new TextFieldDelegate(label, content, required); 2283 m_fields.push_back(FieldDelegateUP(delegate)); 2284 return delegate; 2285 } 2286 2287 FileFieldDelegate *AddFileField(const char *label, const char *content, 2288 bool need_to_exist, bool required) { 2289 FileFieldDelegate *delegate = 2290 new FileFieldDelegate(label, content, need_to_exist, required); 2291 m_fields.push_back(FieldDelegateUP(delegate)); 2292 return delegate; 2293 } 2294 2295 DirectoryFieldDelegate *AddDirectoryField(const char *label, 2296 const char *content, 2297 bool need_to_exist, bool required) { 2298 DirectoryFieldDelegate *delegate = 2299 new DirectoryFieldDelegate(label, content, need_to_exist, required); 2300 m_fields.push_back(FieldDelegateUP(delegate)); 2301 return delegate; 2302 } 2303 2304 ArchFieldDelegate *AddArchField(const char *label, const char *content, 2305 bool required) { 2306 ArchFieldDelegate *delegate = 2307 new ArchFieldDelegate(label, content, required); 2308 m_fields.push_back(FieldDelegateUP(delegate)); 2309 return delegate; 2310 } 2311 2312 IntegerFieldDelegate *AddIntegerField(const char *label, int content, 2313 bool required) { 2314 IntegerFieldDelegate *delegate = 2315 new IntegerFieldDelegate(label, content, required); 2316 m_fields.push_back(FieldDelegateUP(delegate)); 2317 return delegate; 2318 } 2319 2320 BooleanFieldDelegate *AddBooleanField(const char *label, bool content) { 2321 BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content); 2322 m_fields.push_back(FieldDelegateUP(delegate)); 2323 return delegate; 2324 } 2325 2326 LazyBooleanFieldDelegate *AddLazyBooleanField(const char *label, 2327 const char *calculate_label) { 2328 LazyBooleanFieldDelegate *delegate = 2329 new LazyBooleanFieldDelegate(label, calculate_label); 2330 m_fields.push_back(FieldDelegateUP(delegate)); 2331 return delegate; 2332 } 2333 2334 ChoicesFieldDelegate *AddChoicesField(const char *label, int height, 2335 std::vector<std::string> choices) { 2336 ChoicesFieldDelegate *delegate = 2337 new ChoicesFieldDelegate(label, height, choices); 2338 m_fields.push_back(FieldDelegateUP(delegate)); 2339 return delegate; 2340 } 2341 2342 PlatformPluginFieldDelegate *AddPlatformPluginField(Debugger &debugger) { 2343 PlatformPluginFieldDelegate *delegate = 2344 new PlatformPluginFieldDelegate(debugger); 2345 m_fields.push_back(FieldDelegateUP(delegate)); 2346 return delegate; 2347 } 2348 2349 ProcessPluginFieldDelegate *AddProcessPluginField() { 2350 ProcessPluginFieldDelegate *delegate = new ProcessPluginFieldDelegate(); 2351 m_fields.push_back(FieldDelegateUP(delegate)); 2352 return delegate; 2353 } 2354 2355 template <class T> 2356 ListFieldDelegate<T> *AddListField(const char *label, T default_field) { 2357 ListFieldDelegate<T> *delegate = 2358 new ListFieldDelegate<T>(label, default_field); 2359 m_fields.push_back(FieldDelegateUP(delegate)); 2360 return delegate; 2361 } 2362 2363 ArgumentsFieldDelegate *AddArgumentsField() { 2364 ArgumentsFieldDelegate *delegate = new ArgumentsFieldDelegate(); 2365 m_fields.push_back(FieldDelegateUP(delegate)); 2366 return delegate; 2367 } 2368 2369 template <class K, class V> 2370 MappingFieldDelegate<K, V> *AddMappingField(K key_field, V value_field) { 2371 MappingFieldDelegate<K, V> *delegate = 2372 new MappingFieldDelegate<K, V>(key_field, value_field); 2373 m_fields.push_back(FieldDelegateUP(delegate)); 2374 return delegate; 2375 } 2376 2377 EnvironmentVariableNameFieldDelegate * 2378 AddEnvironmentVariableNameField(const char *content) { 2379 EnvironmentVariableNameFieldDelegate *delegate = 2380 new EnvironmentVariableNameFieldDelegate(content); 2381 m_fields.push_back(FieldDelegateUP(delegate)); 2382 return delegate; 2383 } 2384 2385 EnvironmentVariableFieldDelegate *AddEnvironmentVariableField() { 2386 EnvironmentVariableFieldDelegate *delegate = 2387 new EnvironmentVariableFieldDelegate(); 2388 m_fields.push_back(FieldDelegateUP(delegate)); 2389 return delegate; 2390 } 2391 2392 EnvironmentVariableListFieldDelegate * 2393 AddEnvironmentVariableListField(const char *label) { 2394 EnvironmentVariableListFieldDelegate *delegate = 2395 new EnvironmentVariableListFieldDelegate(label); 2396 m_fields.push_back(FieldDelegateUP(delegate)); 2397 return delegate; 2398 } 2399 2400 // Factory methods for adding actions. 2401 2402 void AddAction(const char *label, std::function<void(Window &)> action) { 2403 m_actions.push_back(FormAction(label, action)); 2404 } 2405 2406 protected: 2407 std::vector<FieldDelegateUP> m_fields; 2408 std::vector<FormAction> m_actions; 2409 // Optional error message. If empty, form is considered to have no error. 2410 std::string m_error; 2411 }; 2412 2413 typedef std::shared_ptr<FormDelegate> FormDelegateSP; 2414 2415 class FormWindowDelegate : public WindowDelegate { 2416 public: 2417 FormWindowDelegate(FormDelegateSP &delegate_sp) 2418 : m_delegate_sp(delegate_sp), m_selection_index(0), 2419 m_first_visible_line(0) { 2420 assert(m_delegate_sp->GetNumberOfActions() > 0); 2421 if (m_delegate_sp->GetNumberOfFields() > 0) 2422 m_selection_type = SelectionType::Field; 2423 else 2424 m_selection_type = SelectionType::Action; 2425 } 2426 2427 // Signify which element is selected. If a field or an action is selected, 2428 // then m_selection_index signifies the particular field or action that is 2429 // selected. 2430 enum class SelectionType { Field, Action }; 2431 2432 // A form window is padded by one character from all sides. First, if an error 2433 // message exists, it is drawn followed by a separator. Then one or more 2434 // fields are drawn. Finally, all available actions are drawn on a single 2435 // line. 2436 // 2437 // ___<Form Name>_________________________________________________ 2438 // | | 2439 // | - Error message if it exists. | 2440 // |-------------------------------------------------------------| 2441 // | Form elements here. | 2442 // | Form actions here. | 2443 // | | 2444 // |______________________________________[Press Esc to cancel]__| 2445 // 2446 2447 // One line for the error and another for the horizontal line. 2448 int GetErrorHeight() { 2449 if (m_delegate_sp->HasError()) 2450 return 2; 2451 return 0; 2452 } 2453 2454 // Actions span a single line. 2455 int GetActionsHeight() { 2456 if (m_delegate_sp->GetNumberOfActions() > 0) 2457 return 1; 2458 return 0; 2459 } 2460 2461 // Get the total number of needed lines to draw the contents. 2462 int GetContentHeight() { 2463 int height = 0; 2464 height += GetErrorHeight(); 2465 for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) { 2466 if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible()) 2467 continue; 2468 height += m_delegate_sp->GetField(i)->FieldDelegateGetHeight(); 2469 } 2470 height += GetActionsHeight(); 2471 return height; 2472 } 2473 2474 ScrollContext GetScrollContext() { 2475 if (m_selection_type == SelectionType::Action) 2476 return ScrollContext(GetContentHeight() - 1); 2477 2478 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); 2479 ScrollContext context = field->FieldDelegateGetScrollContext(); 2480 2481 int offset = GetErrorHeight(); 2482 for (int i = 0; i < m_selection_index; i++) { 2483 if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible()) 2484 continue; 2485 offset += m_delegate_sp->GetField(i)->FieldDelegateGetHeight(); 2486 } 2487 context.Offset(offset); 2488 2489 // If the context is touching the error, include the error in the context as 2490 // well. 2491 if (context.start == GetErrorHeight()) 2492 context.start = 0; 2493 2494 return context; 2495 } 2496 2497 void UpdateScrolling(Surface &surface) { 2498 ScrollContext context = GetScrollContext(); 2499 int content_height = GetContentHeight(); 2500 int surface_height = surface.GetHeight(); 2501 int visible_height = std::min(content_height, surface_height); 2502 int last_visible_line = m_first_visible_line + visible_height - 1; 2503 2504 // If the last visible line is bigger than the content, then it is invalid 2505 // and needs to be set to the last line in the content. This can happen when 2506 // a field has shrunk in height. 2507 if (last_visible_line > content_height - 1) { 2508 m_first_visible_line = content_height - visible_height; 2509 } 2510 2511 if (context.start < m_first_visible_line) { 2512 m_first_visible_line = context.start; 2513 return; 2514 } 2515 2516 if (context.end > last_visible_line) { 2517 m_first_visible_line = context.end - visible_height + 1; 2518 } 2519 } 2520 2521 void DrawError(Surface &surface) { 2522 if (!m_delegate_sp->HasError()) 2523 return; 2524 surface.MoveCursor(0, 0); 2525 surface.AttributeOn(COLOR_PAIR(RedOnBlack)); 2526 surface.PutChar(ACS_DIAMOND); 2527 surface.PutChar(' '); 2528 surface.PutCStringTruncated(1, m_delegate_sp->GetError().c_str()); 2529 surface.AttributeOff(COLOR_PAIR(RedOnBlack)); 2530 2531 surface.MoveCursor(0, 1); 2532 surface.HorizontalLine(surface.GetWidth()); 2533 } 2534 2535 void DrawFields(Surface &surface) { 2536 int line = 0; 2537 int width = surface.GetWidth(); 2538 bool a_field_is_selected = m_selection_type == SelectionType::Field; 2539 for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) { 2540 FieldDelegate *field = m_delegate_sp->GetField(i); 2541 if (!field->FieldDelegateIsVisible()) 2542 continue; 2543 bool is_field_selected = a_field_is_selected && m_selection_index == i; 2544 int height = field->FieldDelegateGetHeight(); 2545 Rect bounds = Rect(Point(0, line), Size(width, height)); 2546 Surface field_surface = surface.SubSurface(bounds); 2547 field->FieldDelegateDraw(field_surface, is_field_selected); 2548 line += height; 2549 } 2550 } 2551 2552 void DrawActions(Surface &surface) { 2553 int number_of_actions = m_delegate_sp->GetNumberOfActions(); 2554 int width = surface.GetWidth() / number_of_actions; 2555 bool an_action_is_selected = m_selection_type == SelectionType::Action; 2556 int x = 0; 2557 for (int i = 0; i < number_of_actions; i++) { 2558 bool is_action_selected = an_action_is_selected && m_selection_index == i; 2559 FormAction &action = m_delegate_sp->GetAction(i); 2560 Rect bounds = Rect(Point(x, 0), Size(width, 1)); 2561 Surface action_surface = surface.SubSurface(bounds); 2562 action.Draw(action_surface, is_action_selected); 2563 x += width; 2564 } 2565 } 2566 2567 void DrawElements(Surface &surface) { 2568 Rect frame = surface.GetFrame(); 2569 Rect fields_bounds, actions_bounds; 2570 frame.HorizontalSplit(surface.GetHeight() - GetActionsHeight(), 2571 fields_bounds, actions_bounds); 2572 Surface fields_surface = surface.SubSurface(fields_bounds); 2573 Surface actions_surface = surface.SubSurface(actions_bounds); 2574 2575 DrawFields(fields_surface); 2576 DrawActions(actions_surface); 2577 } 2578 2579 // Contents are first drawn on a pad. Then a subset of that pad is copied to 2580 // the derived window starting at the first visible line. This essentially 2581 // provides scrolling functionality. 2582 void DrawContent(Surface &surface) { 2583 UpdateScrolling(surface); 2584 2585 int width = surface.GetWidth(); 2586 int height = GetContentHeight(); 2587 Pad pad = Pad(Size(width, height)); 2588 2589 Rect frame = pad.GetFrame(); 2590 Rect error_bounds, elements_bounds; 2591 frame.HorizontalSplit(GetErrorHeight(), error_bounds, elements_bounds); 2592 Surface error_surface = pad.SubSurface(error_bounds); 2593 Surface elements_surface = pad.SubSurface(elements_bounds); 2594 2595 DrawError(error_surface); 2596 DrawElements(elements_surface); 2597 2598 int copy_height = std::min(surface.GetHeight(), pad.GetHeight()); 2599 pad.CopyToSurface(surface, Point(0, m_first_visible_line), Point(), 2600 Size(width, copy_height)); 2601 } 2602 2603 bool WindowDelegateDraw(Window &window, bool force) override { 2604 m_delegate_sp->UpdateFieldsVisibility(); 2605 2606 window.Erase(); 2607 2608 window.DrawTitleBox(m_delegate_sp->GetName().c_str(), 2609 "Press Esc to cancel"); 2610 2611 Rect content_bounds = window.GetFrame(); 2612 content_bounds.Inset(2, 2); 2613 Surface content_surface = window.SubSurface(content_bounds); 2614 2615 DrawContent(content_surface); 2616 return true; 2617 } 2618 2619 void SkipNextHiddenFields() { 2620 while (true) { 2621 if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible()) 2622 return; 2623 2624 if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) { 2625 m_selection_type = SelectionType::Action; 2626 m_selection_index = 0; 2627 return; 2628 } 2629 2630 m_selection_index++; 2631 } 2632 } 2633 2634 HandleCharResult SelectNext(int key) { 2635 if (m_selection_type == SelectionType::Action) { 2636 if (m_selection_index < m_delegate_sp->GetNumberOfActions() - 1) { 2637 m_selection_index++; 2638 return eKeyHandled; 2639 } 2640 2641 m_selection_index = 0; 2642 m_selection_type = SelectionType::Field; 2643 SkipNextHiddenFields(); 2644 if (m_selection_type == SelectionType::Field) { 2645 FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index); 2646 next_field->FieldDelegateSelectFirstElement(); 2647 } 2648 return eKeyHandled; 2649 } 2650 2651 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); 2652 if (!field->FieldDelegateOnLastOrOnlyElement()) { 2653 return field->FieldDelegateHandleChar(key); 2654 } 2655 2656 field->FieldDelegateExitCallback(); 2657 2658 if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) { 2659 m_selection_type = SelectionType::Action; 2660 m_selection_index = 0; 2661 return eKeyHandled; 2662 } 2663 2664 m_selection_index++; 2665 SkipNextHiddenFields(); 2666 2667 if (m_selection_type == SelectionType::Field) { 2668 FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index); 2669 next_field->FieldDelegateSelectFirstElement(); 2670 } 2671 2672 return eKeyHandled; 2673 } 2674 2675 void SkipPreviousHiddenFields() { 2676 while (true) { 2677 if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible()) 2678 return; 2679 2680 if (m_selection_index == 0) { 2681 m_selection_type = SelectionType::Action; 2682 m_selection_index = 0; 2683 return; 2684 } 2685 2686 m_selection_index--; 2687 } 2688 } 2689 2690 HandleCharResult SelectPrevious(int key) { 2691 if (m_selection_type == SelectionType::Action) { 2692 if (m_selection_index > 0) { 2693 m_selection_index--; 2694 return eKeyHandled; 2695 } 2696 m_selection_index = m_delegate_sp->GetNumberOfFields() - 1; 2697 m_selection_type = SelectionType::Field; 2698 SkipPreviousHiddenFields(); 2699 if (m_selection_type == SelectionType::Field) { 2700 FieldDelegate *previous_field = 2701 m_delegate_sp->GetField(m_selection_index); 2702 previous_field->FieldDelegateSelectLastElement(); 2703 } 2704 return eKeyHandled; 2705 } 2706 2707 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); 2708 if (!field->FieldDelegateOnFirstOrOnlyElement()) { 2709 return field->FieldDelegateHandleChar(key); 2710 } 2711 2712 field->FieldDelegateExitCallback(); 2713 2714 if (m_selection_index == 0) { 2715 m_selection_type = SelectionType::Action; 2716 m_selection_index = m_delegate_sp->GetNumberOfActions() - 1; 2717 return eKeyHandled; 2718 } 2719 2720 m_selection_index--; 2721 SkipPreviousHiddenFields(); 2722 2723 if (m_selection_type == SelectionType::Field) { 2724 FieldDelegate *previous_field = 2725 m_delegate_sp->GetField(m_selection_index); 2726 previous_field->FieldDelegateSelectLastElement(); 2727 } 2728 2729 return eKeyHandled; 2730 } 2731 2732 void ExecuteAction(Window &window) { 2733 FormAction &action = m_delegate_sp->GetAction(m_selection_index); 2734 action.Execute(window); 2735 if (m_delegate_sp->HasError()) { 2736 m_first_visible_line = 0; 2737 m_selection_index = 0; 2738 m_selection_type = SelectionType::Field; 2739 } 2740 } 2741 2742 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { 2743 switch (key) { 2744 case '\r': 2745 case '\n': 2746 case KEY_ENTER: 2747 if (m_selection_type == SelectionType::Action) { 2748 ExecuteAction(window); 2749 return eKeyHandled; 2750 } 2751 break; 2752 case '\t': 2753 return SelectNext(key); 2754 case KEY_SHIFT_TAB: 2755 return SelectPrevious(key); 2756 case KEY_ESCAPE: 2757 window.GetParent()->RemoveSubWindow(&window); 2758 return eKeyHandled; 2759 default: 2760 break; 2761 } 2762 2763 // If the key wasn't handled and one of the fields is selected, pass the key 2764 // to that field. 2765 if (m_selection_type == SelectionType::Field) { 2766 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); 2767 return field->FieldDelegateHandleChar(key); 2768 } 2769 2770 return eKeyNotHandled; 2771 } 2772 2773 protected: 2774 FormDelegateSP m_delegate_sp; 2775 // The index of the currently selected SelectionType. 2776 int m_selection_index; 2777 // See SelectionType class enum. 2778 SelectionType m_selection_type; 2779 // The first visible line from the pad. 2780 int m_first_visible_line; 2781 }; 2782 2783 /////////////////////////// 2784 // Form Delegate Instances 2785 /////////////////////////// 2786 2787 class DetachOrKillProcessFormDelegate : public FormDelegate { 2788 public: 2789 DetachOrKillProcessFormDelegate(Process *process) : m_process(process) { 2790 SetError("There is a running process, either detach or kill it."); 2791 2792 m_keep_stopped_field = 2793 AddBooleanField("Keep process stopped when detaching.", false); 2794 2795 AddAction("Detach", [this](Window &window) { Detach(window); }); 2796 AddAction("Kill", [this](Window &window) { Kill(window); }); 2797 } 2798 2799 std::string GetName() override { return "Detach/Kill Process"; } 2800 2801 void Kill(Window &window) { 2802 Status destroy_status(m_process->Destroy(false)); 2803 if (destroy_status.Fail()) { 2804 SetError("Failed to kill process."); 2805 return; 2806 } 2807 window.GetParent()->RemoveSubWindow(&window); 2808 } 2809 2810 void Detach(Window &window) { 2811 Status detach_status(m_process->Detach(m_keep_stopped_field->GetBoolean())); 2812 if (detach_status.Fail()) { 2813 SetError("Failed to detach from process."); 2814 return; 2815 } 2816 window.GetParent()->RemoveSubWindow(&window); 2817 } 2818 2819 protected: 2820 Process *m_process; 2821 BooleanFieldDelegate *m_keep_stopped_field; 2822 }; 2823 2824 class ProcessAttachFormDelegate : public FormDelegate { 2825 public: 2826 ProcessAttachFormDelegate(Debugger &debugger, WindowSP main_window_sp) 2827 : m_debugger(debugger), m_main_window_sp(main_window_sp) { 2828 std::vector<std::string> types; 2829 types.push_back(std::string("Name")); 2830 types.push_back(std::string("PID")); 2831 m_type_field = AddChoicesField("Attach By", 2, types); 2832 m_pid_field = AddIntegerField("PID", 0, true); 2833 m_name_field = 2834 AddTextField("Process Name", GetDefaultProcessName().c_str(), true); 2835 m_continue_field = AddBooleanField("Continue once attached.", false); 2836 m_wait_for_field = AddBooleanField("Wait for process to launch.", false); 2837 m_include_existing_field = 2838 AddBooleanField("Include existing processes.", false); 2839 m_show_advanced_field = AddBooleanField("Show advanced settings.", false); 2840 m_plugin_field = AddProcessPluginField(); 2841 2842 AddAction("Attach", [this](Window &window) { Attach(window); }); 2843 } 2844 2845 std::string GetName() override { return "Attach Process"; } 2846 2847 void UpdateFieldsVisibility() override { 2848 if (m_type_field->GetChoiceContent() == "Name") { 2849 m_pid_field->FieldDelegateHide(); 2850 m_name_field->FieldDelegateShow(); 2851 m_wait_for_field->FieldDelegateShow(); 2852 if (m_wait_for_field->GetBoolean()) 2853 m_include_existing_field->FieldDelegateShow(); 2854 else 2855 m_include_existing_field->FieldDelegateHide(); 2856 } else { 2857 m_pid_field->FieldDelegateShow(); 2858 m_name_field->FieldDelegateHide(); 2859 m_wait_for_field->FieldDelegateHide(); 2860 m_include_existing_field->FieldDelegateHide(); 2861 } 2862 if (m_show_advanced_field->GetBoolean()) 2863 m_plugin_field->FieldDelegateShow(); 2864 else 2865 m_plugin_field->FieldDelegateHide(); 2866 } 2867 2868 // Get the basename of the target's main executable if available, empty string 2869 // otherwise. 2870 std::string GetDefaultProcessName() { 2871 Target *target = m_debugger.GetSelectedTarget().get(); 2872 if (target == nullptr) 2873 return ""; 2874 2875 ModuleSP module_sp = target->GetExecutableModule(); 2876 if (!module_sp->IsExecutable()) 2877 return ""; 2878 2879 return module_sp->GetFileSpec().GetFilename().AsCString(); 2880 } 2881 2882 bool StopRunningProcess() { 2883 ExecutionContext exe_ctx = 2884 m_debugger.GetCommandInterpreter().GetExecutionContext(); 2885 2886 if (!exe_ctx.HasProcessScope()) 2887 return false; 2888 2889 Process *process = exe_ctx.GetProcessPtr(); 2890 if (!(process && process->IsAlive())) 2891 return false; 2892 2893 FormDelegateSP form_delegate_sp = 2894 FormDelegateSP(new DetachOrKillProcessFormDelegate(process)); 2895 Rect bounds = m_main_window_sp->GetCenteredRect(85, 8); 2896 WindowSP form_window_sp = m_main_window_sp->CreateSubWindow( 2897 form_delegate_sp->GetName().c_str(), bounds, true); 2898 WindowDelegateSP window_delegate_sp = 2899 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); 2900 form_window_sp->SetDelegate(window_delegate_sp); 2901 2902 return true; 2903 } 2904 2905 Target *GetTarget() { 2906 Target *target = m_debugger.GetSelectedTarget().get(); 2907 2908 if (target != nullptr) 2909 return target; 2910 2911 TargetSP new_target_sp; 2912 m_debugger.GetTargetList().CreateTarget( 2913 m_debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp); 2914 2915 target = new_target_sp.get(); 2916 2917 if (target == nullptr) 2918 SetError("Failed to create target."); 2919 2920 m_debugger.GetTargetList().SetSelectedTarget(new_target_sp); 2921 2922 return target; 2923 } 2924 2925 ProcessAttachInfo GetAttachInfo() { 2926 ProcessAttachInfo attach_info; 2927 attach_info.SetContinueOnceAttached(m_continue_field->GetBoolean()); 2928 if (m_type_field->GetChoiceContent() == "Name") { 2929 attach_info.GetExecutableFile().SetFile(m_name_field->GetText(), 2930 FileSpec::Style::native); 2931 attach_info.SetWaitForLaunch(m_wait_for_field->GetBoolean()); 2932 if (m_wait_for_field->GetBoolean()) 2933 attach_info.SetIgnoreExisting(!m_include_existing_field->GetBoolean()); 2934 } else { 2935 attach_info.SetProcessID(m_pid_field->GetInteger()); 2936 } 2937 attach_info.SetProcessPluginName(m_plugin_field->GetPluginName()); 2938 2939 return attach_info; 2940 } 2941 2942 void Attach(Window &window) { 2943 ClearError(); 2944 2945 bool all_fields_are_valid = CheckFieldsValidity(); 2946 if (!all_fields_are_valid) 2947 return; 2948 2949 bool process_is_running = StopRunningProcess(); 2950 if (process_is_running) 2951 return; 2952 2953 Target *target = GetTarget(); 2954 if (HasError()) 2955 return; 2956 2957 StreamString stream; 2958 ProcessAttachInfo attach_info = GetAttachInfo(); 2959 Status status = target->Attach(attach_info, &stream); 2960 2961 if (status.Fail()) { 2962 SetError(status.AsCString()); 2963 return; 2964 } 2965 2966 ProcessSP process_sp(target->GetProcessSP()); 2967 if (!process_sp) { 2968 SetError("Attached sucessfully but target has no process."); 2969 return; 2970 } 2971 2972 if (attach_info.GetContinueOnceAttached()) 2973 process_sp->Resume(); 2974 2975 window.GetParent()->RemoveSubWindow(&window); 2976 } 2977 2978 protected: 2979 Debugger &m_debugger; 2980 WindowSP m_main_window_sp; 2981 2982 ChoicesFieldDelegate *m_type_field; 2983 IntegerFieldDelegate *m_pid_field; 2984 TextFieldDelegate *m_name_field; 2985 BooleanFieldDelegate *m_continue_field; 2986 BooleanFieldDelegate *m_wait_for_field; 2987 BooleanFieldDelegate *m_include_existing_field; 2988 BooleanFieldDelegate *m_show_advanced_field; 2989 ProcessPluginFieldDelegate *m_plugin_field; 2990 }; 2991 2992 class TargetCreateFormDelegate : public FormDelegate { 2993 public: 2994 TargetCreateFormDelegate(Debugger &debugger) : m_debugger(debugger) { 2995 m_executable_field = AddFileField("Executable", "", /*need_to_exist=*/true, 2996 /*required=*/true); 2997 m_core_file_field = AddFileField("Core File", "", /*need_to_exist=*/true, 2998 /*required=*/false); 2999 m_symbol_file_field = AddFileField( 3000 "Symbol File", "", /*need_to_exist=*/true, /*required=*/false); 3001 m_show_advanced_field = AddBooleanField("Show advanced settings.", false); 3002 m_remote_file_field = AddFileField( 3003 "Remote File", "", /*need_to_exist=*/false, /*required=*/false); 3004 m_arch_field = AddArchField("Architecture", "", /*required=*/false); 3005 m_platform_field = AddPlatformPluginField(debugger); 3006 m_load_dependent_files_field = 3007 AddChoicesField("Load Dependents", 3, GetLoadDependentFilesChoices()); 3008 3009 AddAction("Create", [this](Window &window) { CreateTarget(window); }); 3010 } 3011 3012 std::string GetName() override { return "Create Target"; } 3013 3014 void UpdateFieldsVisibility() override { 3015 if (m_show_advanced_field->GetBoolean()) { 3016 m_remote_file_field->FieldDelegateShow(); 3017 m_arch_field->FieldDelegateShow(); 3018 m_platform_field->FieldDelegateShow(); 3019 m_load_dependent_files_field->FieldDelegateShow(); 3020 } else { 3021 m_remote_file_field->FieldDelegateHide(); 3022 m_arch_field->FieldDelegateHide(); 3023 m_platform_field->FieldDelegateHide(); 3024 m_load_dependent_files_field->FieldDelegateHide(); 3025 } 3026 } 3027 3028 static constexpr const char *kLoadDependentFilesNo = "No"; 3029 static constexpr const char *kLoadDependentFilesYes = "Yes"; 3030 static constexpr const char *kLoadDependentFilesExecOnly = "Executable only"; 3031 3032 std::vector<std::string> GetLoadDependentFilesChoices() { 3033 std::vector<std::string> load_depentents_options; 3034 load_depentents_options.push_back(kLoadDependentFilesExecOnly); 3035 load_depentents_options.push_back(kLoadDependentFilesYes); 3036 load_depentents_options.push_back(kLoadDependentFilesNo); 3037 return load_depentents_options; 3038 } 3039 3040 LoadDependentFiles GetLoadDependentFiles() { 3041 std::string choice = m_load_dependent_files_field->GetChoiceContent(); 3042 if (choice == kLoadDependentFilesNo) 3043 return eLoadDependentsNo; 3044 if (choice == kLoadDependentFilesYes) 3045 return eLoadDependentsYes; 3046 return eLoadDependentsDefault; 3047 } 3048 3049 OptionGroupPlatform GetPlatformOptions() { 3050 OptionGroupPlatform platform_options(false); 3051 platform_options.SetPlatformName(m_platform_field->GetPluginName().c_str()); 3052 return platform_options; 3053 } 3054 3055 TargetSP GetTarget() { 3056 OptionGroupPlatform platform_options = GetPlatformOptions(); 3057 TargetSP target_sp; 3058 Status status = m_debugger.GetTargetList().CreateTarget( 3059 m_debugger, m_executable_field->GetPath(), 3060 m_arch_field->GetArchString(), GetLoadDependentFiles(), 3061 &platform_options, target_sp); 3062 3063 if (status.Fail()) { 3064 SetError(status.AsCString()); 3065 return nullptr; 3066 } 3067 3068 m_debugger.GetTargetList().SetSelectedTarget(target_sp); 3069 3070 return target_sp; 3071 } 3072 3073 void SetSymbolFile(TargetSP target_sp) { 3074 if (!m_symbol_file_field->IsSpecified()) 3075 return; 3076 3077 ModuleSP module_sp(target_sp->GetExecutableModule()); 3078 if (!module_sp) 3079 return; 3080 3081 module_sp->SetSymbolFileFileSpec( 3082 m_symbol_file_field->GetResolvedFileSpec()); 3083 } 3084 3085 void SetCoreFile(TargetSP target_sp) { 3086 if (!m_core_file_field->IsSpecified()) 3087 return; 3088 3089 FileSpec core_file_spec = m_core_file_field->GetResolvedFileSpec(); 3090 3091 FileSpec core_file_directory_spec; 3092 core_file_directory_spec.GetDirectory() = core_file_spec.GetDirectory(); 3093 target_sp->AppendExecutableSearchPaths(core_file_directory_spec); 3094 3095 ProcessSP process_sp(target_sp->CreateProcess( 3096 m_debugger.GetListener(), llvm::StringRef(), &core_file_spec, false)); 3097 3098 if (!process_sp) { 3099 SetError("Unable to find process plug-in for core file!"); 3100 return; 3101 } 3102 3103 Status status = process_sp->LoadCore(); 3104 if (status.Fail()) { 3105 SetError("Can't find plug-in for core file!"); 3106 return; 3107 } 3108 } 3109 3110 void SetRemoteFile(TargetSP target_sp) { 3111 if (!m_remote_file_field->IsSpecified()) 3112 return; 3113 3114 ModuleSP module_sp(target_sp->GetExecutableModule()); 3115 if (!module_sp) 3116 return; 3117 3118 FileSpec remote_file_spec = m_remote_file_field->GetFileSpec(); 3119 module_sp->SetPlatformFileSpec(remote_file_spec); 3120 } 3121 3122 void RemoveTarget(TargetSP target_sp) { 3123 m_debugger.GetTargetList().DeleteTarget(target_sp); 3124 } 3125 3126 void CreateTarget(Window &window) { 3127 ClearError(); 3128 3129 bool all_fields_are_valid = CheckFieldsValidity(); 3130 if (!all_fields_are_valid) 3131 return; 3132 3133 TargetSP target_sp = GetTarget(); 3134 if (HasError()) 3135 return; 3136 3137 SetSymbolFile(target_sp); 3138 if (HasError()) { 3139 RemoveTarget(target_sp); 3140 return; 3141 } 3142 3143 SetCoreFile(target_sp); 3144 if (HasError()) { 3145 RemoveTarget(target_sp); 3146 return; 3147 } 3148 3149 SetRemoteFile(target_sp); 3150 if (HasError()) { 3151 RemoveTarget(target_sp); 3152 return; 3153 } 3154 3155 window.GetParent()->RemoveSubWindow(&window); 3156 } 3157 3158 protected: 3159 Debugger &m_debugger; 3160 3161 FileFieldDelegate *m_executable_field; 3162 FileFieldDelegate *m_core_file_field; 3163 FileFieldDelegate *m_symbol_file_field; 3164 BooleanFieldDelegate *m_show_advanced_field; 3165 FileFieldDelegate *m_remote_file_field; 3166 ArchFieldDelegate *m_arch_field; 3167 PlatformPluginFieldDelegate *m_platform_field; 3168 ChoicesFieldDelegate *m_load_dependent_files_field; 3169 }; 3170 3171 class ProcessLaunchFormDelegate : public FormDelegate { 3172 public: 3173 ProcessLaunchFormDelegate(Debugger &debugger, WindowSP main_window_sp) 3174 : m_debugger(debugger), m_main_window_sp(main_window_sp) { 3175 3176 m_arguments_field = AddArgumentsField(); 3177 SetArgumentsFieldDefaultValue(); 3178 m_target_environment_field = 3179 AddEnvironmentVariableListField("Target Environment Variables"); 3180 SetTargetEnvironmentFieldDefaultValue(); 3181 m_working_directory_field = AddDirectoryField( 3182 "Working Directory", GetDefaultWorkingDirectory().c_str(), true, false); 3183 3184 m_show_advanced_field = AddBooleanField("Show advanced settings.", false); 3185 3186 m_stop_at_entry_field = AddBooleanField("Stop at entry point.", false); 3187 m_detach_on_error_field = 3188 AddBooleanField("Detach on error.", GetDefaultDetachOnError()); 3189 m_disable_aslr_field = 3190 AddBooleanField("Disable ASLR", GetDefaultDisableASLR()); 3191 m_plugin_field = AddProcessPluginField(); 3192 m_arch_field = AddArchField("Architecture", "", false); 3193 m_shell_field = AddFileField("Shell", "", true, false); 3194 m_expand_shell_arguments_field = 3195 AddBooleanField("Expand shell arguments.", false); 3196 3197 m_disable_standard_io_field = 3198 AddBooleanField("Disable Standard IO", GetDefaultDisableStandardIO()); 3199 m_standard_output_field = 3200 AddFileField("Standard Output File", "", /*need_to_exist=*/false, 3201 /*required=*/false); 3202 m_standard_error_field = 3203 AddFileField("Standard Error File", "", /*need_to_exist=*/false, 3204 /*required=*/false); 3205 m_standard_input_field = 3206 AddFileField("Standard Input File", "", /*need_to_exist=*/false, 3207 /*required=*/false); 3208 3209 m_show_inherited_environment_field = 3210 AddBooleanField("Show inherited environment variables.", false); 3211 m_inherited_environment_field = 3212 AddEnvironmentVariableListField("Inherited Environment Variables"); 3213 SetInheritedEnvironmentFieldDefaultValue(); 3214 3215 AddAction("Launch", [this](Window &window) { Launch(window); }); 3216 } 3217 3218 std::string GetName() override { return "Launch Process"; } 3219 3220 void UpdateFieldsVisibility() override { 3221 if (m_show_advanced_field->GetBoolean()) { 3222 m_stop_at_entry_field->FieldDelegateShow(); 3223 m_detach_on_error_field->FieldDelegateShow(); 3224 m_disable_aslr_field->FieldDelegateShow(); 3225 m_plugin_field->FieldDelegateShow(); 3226 m_arch_field->FieldDelegateShow(); 3227 m_shell_field->FieldDelegateShow(); 3228 m_expand_shell_arguments_field->FieldDelegateShow(); 3229 m_disable_standard_io_field->FieldDelegateShow(); 3230 if (m_disable_standard_io_field->GetBoolean()) { 3231 m_standard_input_field->FieldDelegateHide(); 3232 m_standard_output_field->FieldDelegateHide(); 3233 m_standard_error_field->FieldDelegateHide(); 3234 } else { 3235 m_standard_input_field->FieldDelegateShow(); 3236 m_standard_output_field->FieldDelegateShow(); 3237 m_standard_error_field->FieldDelegateShow(); 3238 } 3239 m_show_inherited_environment_field->FieldDelegateShow(); 3240 if (m_show_inherited_environment_field->GetBoolean()) 3241 m_inherited_environment_field->FieldDelegateShow(); 3242 else 3243 m_inherited_environment_field->FieldDelegateHide(); 3244 } else { 3245 m_stop_at_entry_field->FieldDelegateHide(); 3246 m_detach_on_error_field->FieldDelegateHide(); 3247 m_disable_aslr_field->FieldDelegateHide(); 3248 m_plugin_field->FieldDelegateHide(); 3249 m_arch_field->FieldDelegateHide(); 3250 m_shell_field->FieldDelegateHide(); 3251 m_expand_shell_arguments_field->FieldDelegateHide(); 3252 m_disable_standard_io_field->FieldDelegateHide(); 3253 m_standard_input_field->FieldDelegateHide(); 3254 m_standard_output_field->FieldDelegateHide(); 3255 m_standard_error_field->FieldDelegateHide(); 3256 m_show_inherited_environment_field->FieldDelegateHide(); 3257 m_inherited_environment_field->FieldDelegateHide(); 3258 } 3259 } 3260 3261 // Methods for setting the default value of the fields. 3262 3263 void SetArgumentsFieldDefaultValue() { 3264 TargetSP target = m_debugger.GetSelectedTarget(); 3265 if (target == nullptr) 3266 return; 3267 3268 const Args &target_arguments = 3269 target->GetProcessLaunchInfo().GetArguments(); 3270 m_arguments_field->AddArguments(target_arguments); 3271 } 3272 3273 void SetTargetEnvironmentFieldDefaultValue() { 3274 TargetSP target = m_debugger.GetSelectedTarget(); 3275 if (target == nullptr) 3276 return; 3277 3278 const Environment &target_environment = target->GetTargetEnvironment(); 3279 m_target_environment_field->AddEnvironmentVariables(target_environment); 3280 } 3281 3282 void SetInheritedEnvironmentFieldDefaultValue() { 3283 TargetSP target = m_debugger.GetSelectedTarget(); 3284 if (target == nullptr) 3285 return; 3286 3287 const Environment &inherited_environment = 3288 target->GetInheritedEnvironment(); 3289 m_inherited_environment_field->AddEnvironmentVariables( 3290 inherited_environment); 3291 } 3292 3293 std::string GetDefaultWorkingDirectory() { 3294 TargetSP target = m_debugger.GetSelectedTarget(); 3295 if (target == nullptr) 3296 return ""; 3297 3298 PlatformSP platform = target->GetPlatform(); 3299 return platform->GetWorkingDirectory().GetPath(); 3300 } 3301 3302 bool GetDefaultDisableASLR() { 3303 TargetSP target = m_debugger.GetSelectedTarget(); 3304 if (target == nullptr) 3305 return false; 3306 3307 return target->GetDisableASLR(); 3308 } 3309 3310 bool GetDefaultDisableStandardIO() { 3311 TargetSP target = m_debugger.GetSelectedTarget(); 3312 if (target == nullptr) 3313 return true; 3314 3315 return target->GetDisableSTDIO(); 3316 } 3317 3318 bool GetDefaultDetachOnError() { 3319 TargetSP target = m_debugger.GetSelectedTarget(); 3320 if (target == nullptr) 3321 return true; 3322 3323 return target->GetDetachOnError(); 3324 } 3325 3326 // Methods for getting the necessary information and setting them to the 3327 // ProcessLaunchInfo. 3328 3329 void GetExecutableSettings(ProcessLaunchInfo &launch_info) { 3330 TargetSP target = m_debugger.GetSelectedTarget(); 3331 ModuleSP executable_module = target->GetExecutableModule(); 3332 llvm::StringRef target_settings_argv0 = target->GetArg0(); 3333 3334 if (!target_settings_argv0.empty()) { 3335 launch_info.GetArguments().AppendArgument(target_settings_argv0); 3336 launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(), 3337 false); 3338 return; 3339 } 3340 3341 launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(), 3342 true); 3343 } 3344 3345 void GetArguments(ProcessLaunchInfo &launch_info) { 3346 TargetSP target = m_debugger.GetSelectedTarget(); 3347 Args arguments = m_arguments_field->GetArguments(); 3348 launch_info.GetArguments().AppendArguments(arguments); 3349 } 3350 3351 void GetEnvironment(ProcessLaunchInfo &launch_info) { 3352 Environment target_environment = 3353 m_target_environment_field->GetEnvironment(); 3354 Environment inherited_environment = 3355 m_inherited_environment_field->GetEnvironment(); 3356 launch_info.GetEnvironment().insert(target_environment.begin(), 3357 target_environment.end()); 3358 launch_info.GetEnvironment().insert(inherited_environment.begin(), 3359 inherited_environment.end()); 3360 } 3361 3362 void GetWorkingDirectory(ProcessLaunchInfo &launch_info) { 3363 if (m_working_directory_field->IsSpecified()) 3364 launch_info.SetWorkingDirectory( 3365 m_working_directory_field->GetResolvedFileSpec()); 3366 } 3367 3368 void GetStopAtEntry(ProcessLaunchInfo &launch_info) { 3369 if (m_stop_at_entry_field->GetBoolean()) 3370 launch_info.GetFlags().Set(eLaunchFlagStopAtEntry); 3371 else 3372 launch_info.GetFlags().Clear(eLaunchFlagStopAtEntry); 3373 } 3374 3375 void GetDetachOnError(ProcessLaunchInfo &launch_info) { 3376 if (m_detach_on_error_field->GetBoolean()) 3377 launch_info.GetFlags().Set(eLaunchFlagDetachOnError); 3378 else 3379 launch_info.GetFlags().Clear(eLaunchFlagDetachOnError); 3380 } 3381 3382 void GetDisableASLR(ProcessLaunchInfo &launch_info) { 3383 if (m_disable_aslr_field->GetBoolean()) 3384 launch_info.GetFlags().Set(eLaunchFlagDisableASLR); 3385 else 3386 launch_info.GetFlags().Clear(eLaunchFlagDisableASLR); 3387 } 3388 3389 void GetPlugin(ProcessLaunchInfo &launch_info) { 3390 launch_info.SetProcessPluginName(m_plugin_field->GetPluginName()); 3391 } 3392 3393 void GetArch(ProcessLaunchInfo &launch_info) { 3394 if (!m_arch_field->IsSpecified()) 3395 return; 3396 3397 TargetSP target_sp = m_debugger.GetSelectedTarget(); 3398 PlatformSP platform_sp = 3399 target_sp ? target_sp->GetPlatform() : PlatformSP(); 3400 launch_info.GetArchitecture() = Platform::GetAugmentedArchSpec( 3401 platform_sp.get(), m_arch_field->GetArchString()); 3402 } 3403 3404 void GetShell(ProcessLaunchInfo &launch_info) { 3405 if (!m_shell_field->IsSpecified()) 3406 return; 3407 3408 launch_info.SetShell(m_shell_field->GetResolvedFileSpec()); 3409 launch_info.SetShellExpandArguments( 3410 m_expand_shell_arguments_field->GetBoolean()); 3411 } 3412 3413 void GetStandardIO(ProcessLaunchInfo &launch_info) { 3414 if (m_disable_standard_io_field->GetBoolean()) { 3415 launch_info.GetFlags().Set(eLaunchFlagDisableSTDIO); 3416 return; 3417 } 3418 3419 FileAction action; 3420 if (m_standard_input_field->IsSpecified()) { 3421 action.Open(STDIN_FILENO, m_standard_input_field->GetFileSpec(), true, 3422 false); 3423 launch_info.AppendFileAction(action); 3424 } 3425 if (m_standard_output_field->IsSpecified()) { 3426 action.Open(STDOUT_FILENO, m_standard_output_field->GetFileSpec(), false, 3427 true); 3428 launch_info.AppendFileAction(action); 3429 } 3430 if (m_standard_error_field->IsSpecified()) { 3431 action.Open(STDERR_FILENO, m_standard_error_field->GetFileSpec(), false, 3432 true); 3433 launch_info.AppendFileAction(action); 3434 } 3435 } 3436 3437 void GetInheritTCC(ProcessLaunchInfo &launch_info) { 3438 if (m_debugger.GetSelectedTarget()->GetInheritTCC()) 3439 launch_info.GetFlags().Set(eLaunchFlagInheritTCCFromParent); 3440 } 3441 3442 ProcessLaunchInfo GetLaunchInfo() { 3443 ProcessLaunchInfo launch_info; 3444 3445 GetExecutableSettings(launch_info); 3446 GetArguments(launch_info); 3447 GetEnvironment(launch_info); 3448 GetWorkingDirectory(launch_info); 3449 GetStopAtEntry(launch_info); 3450 GetDetachOnError(launch_info); 3451 GetDisableASLR(launch_info); 3452 GetPlugin(launch_info); 3453 GetArch(launch_info); 3454 GetShell(launch_info); 3455 GetStandardIO(launch_info); 3456 GetInheritTCC(launch_info); 3457 3458 return launch_info; 3459 } 3460 3461 bool StopRunningProcess() { 3462 ExecutionContext exe_ctx = 3463 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3464 3465 if (!exe_ctx.HasProcessScope()) 3466 return false; 3467 3468 Process *process = exe_ctx.GetProcessPtr(); 3469 if (!(process && process->IsAlive())) 3470 return false; 3471 3472 FormDelegateSP form_delegate_sp = 3473 FormDelegateSP(new DetachOrKillProcessFormDelegate(process)); 3474 Rect bounds = m_main_window_sp->GetCenteredRect(85, 8); 3475 WindowSP form_window_sp = m_main_window_sp->CreateSubWindow( 3476 form_delegate_sp->GetName().c_str(), bounds, true); 3477 WindowDelegateSP window_delegate_sp = 3478 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); 3479 form_window_sp->SetDelegate(window_delegate_sp); 3480 3481 return true; 3482 } 3483 3484 Target *GetTarget() { 3485 Target *target = m_debugger.GetSelectedTarget().get(); 3486 3487 if (target == nullptr) { 3488 SetError("No target exists!"); 3489 return nullptr; 3490 } 3491 3492 ModuleSP exe_module_sp = target->GetExecutableModule(); 3493 3494 if (exe_module_sp == nullptr) { 3495 SetError("No executable in target!"); 3496 return nullptr; 3497 } 3498 3499 return target; 3500 } 3501 3502 void Launch(Window &window) { 3503 ClearError(); 3504 3505 bool all_fields_are_valid = CheckFieldsValidity(); 3506 if (!all_fields_are_valid) 3507 return; 3508 3509 bool process_is_running = StopRunningProcess(); 3510 if (process_is_running) 3511 return; 3512 3513 Target *target = GetTarget(); 3514 if (HasError()) 3515 return; 3516 3517 StreamString stream; 3518 ProcessLaunchInfo launch_info = GetLaunchInfo(); 3519 Status status = target->Launch(launch_info, &stream); 3520 3521 if (status.Fail()) { 3522 SetError(status.AsCString()); 3523 return; 3524 } 3525 3526 ProcessSP process_sp(target->GetProcessSP()); 3527 if (!process_sp) { 3528 SetError("Launched successfully but target has no process!"); 3529 return; 3530 } 3531 3532 window.GetParent()->RemoveSubWindow(&window); 3533 } 3534 3535 protected: 3536 Debugger &m_debugger; 3537 WindowSP m_main_window_sp; 3538 3539 ArgumentsFieldDelegate *m_arguments_field; 3540 EnvironmentVariableListFieldDelegate *m_target_environment_field; 3541 DirectoryFieldDelegate *m_working_directory_field; 3542 3543 BooleanFieldDelegate *m_show_advanced_field; 3544 3545 BooleanFieldDelegate *m_stop_at_entry_field; 3546 BooleanFieldDelegate *m_detach_on_error_field; 3547 BooleanFieldDelegate *m_disable_aslr_field; 3548 ProcessPluginFieldDelegate *m_plugin_field; 3549 ArchFieldDelegate *m_arch_field; 3550 FileFieldDelegate *m_shell_field; 3551 BooleanFieldDelegate *m_expand_shell_arguments_field; 3552 BooleanFieldDelegate *m_disable_standard_io_field; 3553 FileFieldDelegate *m_standard_input_field; 3554 FileFieldDelegate *m_standard_output_field; 3555 FileFieldDelegate *m_standard_error_field; 3556 3557 BooleanFieldDelegate *m_show_inherited_environment_field; 3558 EnvironmentVariableListFieldDelegate *m_inherited_environment_field; 3559 }; 3560 3561 class MenuDelegate { 3562 public: 3563 virtual ~MenuDelegate() = default; 3564 3565 virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0; 3566 }; 3567 3568 class Menu : public WindowDelegate { 3569 public: 3570 enum class Type { Invalid, Bar, Item, Separator }; 3571 3572 // Menubar or separator constructor 3573 Menu(Type type); 3574 3575 // Menuitem constructor 3576 Menu(const char *name, const char *key_name, int key_value, 3577 uint64_t identifier); 3578 3579 ~Menu() override = default; 3580 3581 const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; } 3582 3583 void SetDelegate(const MenuDelegateSP &delegate_sp) { 3584 m_delegate_sp = delegate_sp; 3585 } 3586 3587 void RecalculateNameLengths(); 3588 3589 void AddSubmenu(const MenuSP &menu_sp); 3590 3591 int DrawAndRunMenu(Window &window); 3592 3593 void DrawMenuTitle(Window &window, bool highlight); 3594 3595 bool WindowDelegateDraw(Window &window, bool force) override; 3596 3597 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override; 3598 3599 MenuActionResult ActionPrivate(Menu &menu) { 3600 MenuActionResult result = MenuActionResult::NotHandled; 3601 if (m_delegate_sp) { 3602 result = m_delegate_sp->MenuDelegateAction(menu); 3603 if (result != MenuActionResult::NotHandled) 3604 return result; 3605 } else if (m_parent) { 3606 result = m_parent->ActionPrivate(menu); 3607 if (result != MenuActionResult::NotHandled) 3608 return result; 3609 } 3610 return m_canned_result; 3611 } 3612 3613 MenuActionResult Action() { 3614 // Call the recursive action so it can try to handle it with the menu 3615 // delegate, and if not, try our parent menu 3616 return ActionPrivate(*this); 3617 } 3618 3619 void SetCannedResult(MenuActionResult result) { m_canned_result = result; } 3620 3621 Menus &GetSubmenus() { return m_submenus; } 3622 3623 const Menus &GetSubmenus() const { return m_submenus; } 3624 3625 int GetSelectedSubmenuIndex() const { return m_selected; } 3626 3627 void SetSelectedSubmenuIndex(int idx) { m_selected = idx; } 3628 3629 Type GetType() const { return m_type; } 3630 3631 int GetStartingColumn() const { return m_start_col; } 3632 3633 void SetStartingColumn(int col) { m_start_col = col; } 3634 3635 int GetKeyValue() const { return m_key_value; } 3636 3637 std::string &GetName() { return m_name; } 3638 3639 int GetDrawWidth() const { 3640 return m_max_submenu_name_length + m_max_submenu_key_name_length + 8; 3641 } 3642 3643 uint64_t GetIdentifier() const { return m_identifier; } 3644 3645 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } 3646 3647 protected: 3648 std::string m_name; 3649 std::string m_key_name; 3650 uint64_t m_identifier; 3651 Type m_type; 3652 int m_key_value; 3653 int m_start_col; 3654 int m_max_submenu_name_length; 3655 int m_max_submenu_key_name_length; 3656 int m_selected; 3657 Menu *m_parent; 3658 Menus m_submenus; 3659 WindowSP m_menu_window_sp; 3660 MenuActionResult m_canned_result; 3661 MenuDelegateSP m_delegate_sp; 3662 }; 3663 3664 // Menubar or separator constructor 3665 Menu::Menu(Type type) 3666 : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0), 3667 m_start_col(0), m_max_submenu_name_length(0), 3668 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr), 3669 m_submenus(), m_canned_result(MenuActionResult::NotHandled), 3670 m_delegate_sp() {} 3671 3672 // Menuitem constructor 3673 Menu::Menu(const char *name, const char *key_name, int key_value, 3674 uint64_t identifier) 3675 : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid), 3676 m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0), 3677 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr), 3678 m_submenus(), m_canned_result(MenuActionResult::NotHandled), 3679 m_delegate_sp() { 3680 if (name && name[0]) { 3681 m_name = name; 3682 m_type = Type::Item; 3683 if (key_name && key_name[0]) 3684 m_key_name = key_name; 3685 } else { 3686 m_type = Type::Separator; 3687 } 3688 } 3689 3690 void Menu::RecalculateNameLengths() { 3691 m_max_submenu_name_length = 0; 3692 m_max_submenu_key_name_length = 0; 3693 Menus &submenus = GetSubmenus(); 3694 const size_t num_submenus = submenus.size(); 3695 for (size_t i = 0; i < num_submenus; ++i) { 3696 Menu *submenu = submenus[i].get(); 3697 if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size()) 3698 m_max_submenu_name_length = submenu->m_name.size(); 3699 if (static_cast<size_t>(m_max_submenu_key_name_length) < 3700 submenu->m_key_name.size()) 3701 m_max_submenu_key_name_length = submenu->m_key_name.size(); 3702 } 3703 } 3704 3705 void Menu::AddSubmenu(const MenuSP &menu_sp) { 3706 menu_sp->m_parent = this; 3707 if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size()) 3708 m_max_submenu_name_length = menu_sp->m_name.size(); 3709 if (static_cast<size_t>(m_max_submenu_key_name_length) < 3710 menu_sp->m_key_name.size()) 3711 m_max_submenu_key_name_length = menu_sp->m_key_name.size(); 3712 m_submenus.push_back(menu_sp); 3713 } 3714 3715 void Menu::DrawMenuTitle(Window &window, bool highlight) { 3716 if (m_type == Type::Separator) { 3717 window.MoveCursor(0, window.GetCursorY()); 3718 window.PutChar(ACS_LTEE); 3719 int width = window.GetWidth(); 3720 if (width > 2) { 3721 width -= 2; 3722 for (int i = 0; i < width; ++i) 3723 window.PutChar(ACS_HLINE); 3724 } 3725 window.PutChar(ACS_RTEE); 3726 } else { 3727 const int shortcut_key = m_key_value; 3728 bool underlined_shortcut = false; 3729 const attr_t highlight_attr = A_REVERSE; 3730 if (highlight) 3731 window.AttributeOn(highlight_attr); 3732 if (llvm::isPrint(shortcut_key)) { 3733 size_t lower_pos = m_name.find(tolower(shortcut_key)); 3734 size_t upper_pos = m_name.find(toupper(shortcut_key)); 3735 const char *name = m_name.c_str(); 3736 size_t pos = std::min<size_t>(lower_pos, upper_pos); 3737 if (pos != std::string::npos) { 3738 underlined_shortcut = true; 3739 if (pos > 0) { 3740 window.PutCString(name, pos); 3741 name += pos; 3742 } 3743 const attr_t shortcut_attr = A_UNDERLINE | A_BOLD; 3744 window.AttributeOn(shortcut_attr); 3745 window.PutChar(name[0]); 3746 window.AttributeOff(shortcut_attr); 3747 name++; 3748 if (name[0]) 3749 window.PutCString(name); 3750 } 3751 } 3752 3753 if (!underlined_shortcut) { 3754 window.PutCString(m_name.c_str()); 3755 } 3756 3757 if (highlight) 3758 window.AttributeOff(highlight_attr); 3759 3760 if (m_key_name.empty()) { 3761 if (!underlined_shortcut && llvm::isPrint(m_key_value)) { 3762 window.AttributeOn(COLOR_PAIR(MagentaOnWhite)); 3763 window.Printf(" (%c)", m_key_value); 3764 window.AttributeOff(COLOR_PAIR(MagentaOnWhite)); 3765 } 3766 } else { 3767 window.AttributeOn(COLOR_PAIR(MagentaOnWhite)); 3768 window.Printf(" (%s)", m_key_name.c_str()); 3769 window.AttributeOff(COLOR_PAIR(MagentaOnWhite)); 3770 } 3771 } 3772 } 3773 3774 bool Menu::WindowDelegateDraw(Window &window, bool force) { 3775 Menus &submenus = GetSubmenus(); 3776 const size_t num_submenus = submenus.size(); 3777 const int selected_idx = GetSelectedSubmenuIndex(); 3778 Menu::Type menu_type = GetType(); 3779 switch (menu_type) { 3780 case Menu::Type::Bar: { 3781 window.SetBackground(BlackOnWhite); 3782 window.MoveCursor(0, 0); 3783 for (size_t i = 0; i < num_submenus; ++i) { 3784 Menu *menu = submenus[i].get(); 3785 if (i > 0) 3786 window.PutChar(' '); 3787 menu->SetStartingColumn(window.GetCursorX()); 3788 window.PutCString("| "); 3789 menu->DrawMenuTitle(window, false); 3790 } 3791 window.PutCString(" |"); 3792 } break; 3793 3794 case Menu::Type::Item: { 3795 int y = 1; 3796 int x = 3; 3797 // Draw the menu 3798 int cursor_x = 0; 3799 int cursor_y = 0; 3800 window.Erase(); 3801 window.SetBackground(BlackOnWhite); 3802 window.Box(); 3803 for (size_t i = 0; i < num_submenus; ++i) { 3804 const bool is_selected = (i == static_cast<size_t>(selected_idx)); 3805 window.MoveCursor(x, y + i); 3806 if (is_selected) { 3807 // Remember where we want the cursor to be 3808 cursor_x = x - 1; 3809 cursor_y = y + i; 3810 } 3811 submenus[i]->DrawMenuTitle(window, is_selected); 3812 } 3813 window.MoveCursor(cursor_x, cursor_y); 3814 } break; 3815 3816 default: 3817 case Menu::Type::Separator: 3818 break; 3819 } 3820 return true; // Drawing handled... 3821 } 3822 3823 HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) { 3824 HandleCharResult result = eKeyNotHandled; 3825 3826 Menus &submenus = GetSubmenus(); 3827 const size_t num_submenus = submenus.size(); 3828 const int selected_idx = GetSelectedSubmenuIndex(); 3829 Menu::Type menu_type = GetType(); 3830 if (menu_type == Menu::Type::Bar) { 3831 MenuSP run_menu_sp; 3832 switch (key) { 3833 case KEY_DOWN: 3834 case KEY_UP: 3835 // Show last menu or first menu 3836 if (selected_idx < static_cast<int>(num_submenus)) 3837 run_menu_sp = submenus[selected_idx]; 3838 else if (!submenus.empty()) 3839 run_menu_sp = submenus.front(); 3840 result = eKeyHandled; 3841 break; 3842 3843 case KEY_RIGHT: 3844 ++m_selected; 3845 if (m_selected >= static_cast<int>(num_submenus)) 3846 m_selected = 0; 3847 if (m_selected < static_cast<int>(num_submenus)) 3848 run_menu_sp = submenus[m_selected]; 3849 else if (!submenus.empty()) 3850 run_menu_sp = submenus.front(); 3851 result = eKeyHandled; 3852 break; 3853 3854 case KEY_LEFT: 3855 --m_selected; 3856 if (m_selected < 0) 3857 m_selected = num_submenus - 1; 3858 if (m_selected < static_cast<int>(num_submenus)) 3859 run_menu_sp = submenus[m_selected]; 3860 else if (!submenus.empty()) 3861 run_menu_sp = submenus.front(); 3862 result = eKeyHandled; 3863 break; 3864 3865 default: 3866 for (size_t i = 0; i < num_submenus; ++i) { 3867 if (submenus[i]->GetKeyValue() == key) { 3868 SetSelectedSubmenuIndex(i); 3869 run_menu_sp = submenus[i]; 3870 result = eKeyHandled; 3871 break; 3872 } 3873 } 3874 break; 3875 } 3876 3877 if (run_menu_sp) { 3878 // Run the action on this menu in case we need to populate the menu with 3879 // dynamic content and also in case check marks, and any other menu 3880 // decorations need to be calculated 3881 if (run_menu_sp->Action() == MenuActionResult::Quit) 3882 return eQuitApplication; 3883 3884 Rect menu_bounds; 3885 menu_bounds.origin.x = run_menu_sp->GetStartingColumn(); 3886 menu_bounds.origin.y = 1; 3887 menu_bounds.size.width = run_menu_sp->GetDrawWidth(); 3888 menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2; 3889 if (m_menu_window_sp) 3890 window.GetParent()->RemoveSubWindow(m_menu_window_sp.get()); 3891 3892 m_menu_window_sp = window.GetParent()->CreateSubWindow( 3893 run_menu_sp->GetName().c_str(), menu_bounds, true); 3894 m_menu_window_sp->SetDelegate(run_menu_sp); 3895 } 3896 } else if (menu_type == Menu::Type::Item) { 3897 switch (key) { 3898 case KEY_DOWN: 3899 if (m_submenus.size() > 1) { 3900 const int start_select = m_selected; 3901 while (++m_selected != start_select) { 3902 if (static_cast<size_t>(m_selected) >= num_submenus) 3903 m_selected = 0; 3904 if (m_submenus[m_selected]->GetType() == Type::Separator) 3905 continue; 3906 else 3907 break; 3908 } 3909 return eKeyHandled; 3910 } 3911 break; 3912 3913 case KEY_UP: 3914 if (m_submenus.size() > 1) { 3915 const int start_select = m_selected; 3916 while (--m_selected != start_select) { 3917 if (m_selected < static_cast<int>(0)) 3918 m_selected = num_submenus - 1; 3919 if (m_submenus[m_selected]->GetType() == Type::Separator) 3920 continue; 3921 else 3922 break; 3923 } 3924 return eKeyHandled; 3925 } 3926 break; 3927 3928 case KEY_RETURN: 3929 if (static_cast<size_t>(selected_idx) < num_submenus) { 3930 if (submenus[selected_idx]->Action() == MenuActionResult::Quit) 3931 return eQuitApplication; 3932 window.GetParent()->RemoveSubWindow(&window); 3933 return eKeyHandled; 3934 } 3935 break; 3936 3937 case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in 3938 // case other chars are entered for escaped sequences 3939 window.GetParent()->RemoveSubWindow(&window); 3940 return eKeyHandled; 3941 3942 default: 3943 for (size_t i = 0; i < num_submenus; ++i) { 3944 Menu *menu = submenus[i].get(); 3945 if (menu->GetKeyValue() == key) { 3946 SetSelectedSubmenuIndex(i); 3947 window.GetParent()->RemoveSubWindow(&window); 3948 if (menu->Action() == MenuActionResult::Quit) 3949 return eQuitApplication; 3950 return eKeyHandled; 3951 } 3952 } 3953 break; 3954 } 3955 } else if (menu_type == Menu::Type::Separator) { 3956 } 3957 return result; 3958 } 3959 3960 class Application { 3961 public: 3962 Application(FILE *in, FILE *out) 3963 : m_window_sp(), m_screen(nullptr), m_in(in), m_out(out) {} 3964 3965 ~Application() { 3966 m_window_delegates.clear(); 3967 m_window_sp.reset(); 3968 if (m_screen) { 3969 ::delscreen(m_screen); 3970 m_screen = nullptr; 3971 } 3972 } 3973 3974 void Initialize() { 3975 m_screen = ::newterm(nullptr, m_out, m_in); 3976 ::start_color(); 3977 ::curs_set(0); 3978 ::noecho(); 3979 ::keypad(stdscr, TRUE); 3980 } 3981 3982 void Terminate() { ::endwin(); } 3983 3984 void Run(Debugger &debugger) { 3985 bool done = false; 3986 int delay_in_tenths_of_a_second = 1; 3987 3988 // Alas the threading model in curses is a bit lame so we need to resort 3989 // to polling every 0.5 seconds. We could poll for stdin ourselves and 3990 // then pass the keys down but then we need to translate all of the escape 3991 // sequences ourselves. So we resort to polling for input because we need 3992 // to receive async process events while in this loop. 3993 3994 halfdelay(delay_in_tenths_of_a_second); // Poll using some number of 3995 // tenths of seconds seconds when 3996 // calling Window::GetChar() 3997 3998 ListenerSP listener_sp( 3999 Listener::MakeListener("lldb.IOHandler.curses.Application")); 4000 ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass()); 4001 debugger.EnableForwardEvents(listener_sp); 4002 4003 m_update_screen = true; 4004 #if defined(__APPLE__) 4005 std::deque<int> escape_chars; 4006 #endif 4007 4008 while (!done) { 4009 if (m_update_screen) { 4010 m_window_sp->Draw(false); 4011 // All windows should be calling Window::DeferredRefresh() instead of 4012 // Window::Refresh() so we can do a single update and avoid any screen 4013 // blinking 4014 update_panels(); 4015 4016 // Cursor hiding isn't working on MacOSX, so hide it in the top left 4017 // corner 4018 m_window_sp->MoveCursor(0, 0); 4019 4020 doupdate(); 4021 m_update_screen = false; 4022 } 4023 4024 #if defined(__APPLE__) 4025 // Terminal.app doesn't map its function keys correctly, F1-F4 default 4026 // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if 4027 // possible 4028 int ch; 4029 if (escape_chars.empty()) 4030 ch = m_window_sp->GetChar(); 4031 else { 4032 ch = escape_chars.front(); 4033 escape_chars.pop_front(); 4034 } 4035 if (ch == KEY_ESCAPE) { 4036 int ch2 = m_window_sp->GetChar(); 4037 if (ch2 == 'O') { 4038 int ch3 = m_window_sp->GetChar(); 4039 switch (ch3) { 4040 case 'P': 4041 ch = KEY_F(1); 4042 break; 4043 case 'Q': 4044 ch = KEY_F(2); 4045 break; 4046 case 'R': 4047 ch = KEY_F(3); 4048 break; 4049 case 'S': 4050 ch = KEY_F(4); 4051 break; 4052 default: 4053 escape_chars.push_back(ch2); 4054 if (ch3 != -1) 4055 escape_chars.push_back(ch3); 4056 break; 4057 } 4058 } else if (ch2 != -1) 4059 escape_chars.push_back(ch2); 4060 } 4061 #else 4062 int ch = m_window_sp->GetChar(); 4063 4064 #endif 4065 if (ch == -1) { 4066 if (feof(m_in) || ferror(m_in)) { 4067 done = true; 4068 } else { 4069 // Just a timeout from using halfdelay(), check for events 4070 EventSP event_sp; 4071 while (listener_sp->PeekAtNextEvent()) { 4072 listener_sp->GetEvent(event_sp, std::chrono::seconds(0)); 4073 4074 if (event_sp) { 4075 Broadcaster *broadcaster = event_sp->GetBroadcaster(); 4076 if (broadcaster) { 4077 // uint32_t event_type = event_sp->GetType(); 4078 ConstString broadcaster_class( 4079 broadcaster->GetBroadcasterClass()); 4080 if (broadcaster_class == broadcaster_class_process) { 4081 m_update_screen = true; 4082 continue; // Don't get any key, just update our view 4083 } 4084 } 4085 } 4086 } 4087 } 4088 } else { 4089 HandleCharResult key_result = m_window_sp->HandleChar(ch); 4090 switch (key_result) { 4091 case eKeyHandled: 4092 m_update_screen = true; 4093 break; 4094 case eKeyNotHandled: 4095 if (ch == 12) { // Ctrl+L, force full redraw 4096 redrawwin(m_window_sp->get()); 4097 m_update_screen = true; 4098 } 4099 break; 4100 case eQuitApplication: 4101 done = true; 4102 break; 4103 } 4104 } 4105 } 4106 4107 debugger.CancelForwardEvents(listener_sp); 4108 } 4109 4110 WindowSP &GetMainWindow() { 4111 if (!m_window_sp) 4112 m_window_sp = std::make_shared<Window>("main", stdscr, false); 4113 return m_window_sp; 4114 } 4115 4116 void TerminalSizeChanged() { 4117 ::endwin(); 4118 ::refresh(); 4119 Rect content_bounds = m_window_sp->GetFrame(); 4120 m_window_sp->SetBounds(content_bounds); 4121 if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar")) 4122 menubar_window_sp->SetBounds(content_bounds.MakeMenuBar()); 4123 if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status")) 4124 status_window_sp->SetBounds(content_bounds.MakeStatusBar()); 4125 4126 WindowSP source_window_sp = m_window_sp->FindSubWindow("Source"); 4127 WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables"); 4128 WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers"); 4129 WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads"); 4130 4131 Rect threads_bounds; 4132 Rect source_variables_bounds; 4133 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, 4134 threads_bounds); 4135 if (threads_window_sp) 4136 threads_window_sp->SetBounds(threads_bounds); 4137 else 4138 source_variables_bounds = content_bounds; 4139 4140 Rect source_bounds; 4141 Rect variables_registers_bounds; 4142 source_variables_bounds.HorizontalSplitPercentage( 4143 0.70, source_bounds, variables_registers_bounds); 4144 if (variables_window_sp || registers_window_sp) { 4145 if (variables_window_sp && registers_window_sp) { 4146 Rect variables_bounds; 4147 Rect registers_bounds; 4148 variables_registers_bounds.VerticalSplitPercentage( 4149 0.50, variables_bounds, registers_bounds); 4150 variables_window_sp->SetBounds(variables_bounds); 4151 registers_window_sp->SetBounds(registers_bounds); 4152 } else if (variables_window_sp) { 4153 variables_window_sp->SetBounds(variables_registers_bounds); 4154 } else { 4155 registers_window_sp->SetBounds(variables_registers_bounds); 4156 } 4157 } else { 4158 source_bounds = source_variables_bounds; 4159 } 4160 4161 source_window_sp->SetBounds(source_bounds); 4162 4163 touchwin(stdscr); 4164 redrawwin(m_window_sp->get()); 4165 m_update_screen = true; 4166 } 4167 4168 protected: 4169 WindowSP m_window_sp; 4170 WindowDelegates m_window_delegates; 4171 SCREEN *m_screen; 4172 FILE *m_in; 4173 FILE *m_out; 4174 bool m_update_screen = false; 4175 }; 4176 4177 } // namespace curses 4178 4179 using namespace curses; 4180 4181 struct Row { 4182 ValueObjectUpdater value; 4183 Row *parent; 4184 // The process stop ID when the children were calculated. 4185 uint32_t children_stop_id = 0; 4186 int row_idx = 0; 4187 int x = 1; 4188 int y = 1; 4189 bool might_have_children; 4190 bool expanded = false; 4191 bool calculated_children = false; 4192 std::vector<Row> children; 4193 4194 Row(const ValueObjectSP &v, Row *p) 4195 : value(v), parent(p), 4196 might_have_children(v ? v->MightHaveChildren() : false) {} 4197 4198 size_t GetDepth() const { 4199 if (parent) 4200 return 1 + parent->GetDepth(); 4201 return 0; 4202 } 4203 4204 void Expand() { expanded = true; } 4205 4206 std::vector<Row> &GetChildren() { 4207 ProcessSP process_sp = value.GetProcessSP(); 4208 auto stop_id = process_sp->GetStopID(); 4209 if (process_sp && stop_id != children_stop_id) { 4210 children_stop_id = stop_id; 4211 calculated_children = false; 4212 } 4213 if (!calculated_children) { 4214 children.clear(); 4215 calculated_children = true; 4216 ValueObjectSP valobj = value.GetSP(); 4217 if (valobj) { 4218 const size_t num_children = valobj->GetNumChildren(); 4219 for (size_t i = 0; i < num_children; ++i) { 4220 children.push_back(Row(valobj->GetChildAtIndex(i, true), this)); 4221 } 4222 } 4223 } 4224 return children; 4225 } 4226 4227 void Unexpand() { 4228 expanded = false; 4229 calculated_children = false; 4230 children.clear(); 4231 } 4232 4233 void DrawTree(Window &window) { 4234 if (parent) 4235 parent->DrawTreeForChild(window, this, 0); 4236 4237 if (might_have_children) { 4238 // It we can get UTF8 characters to work we should try to use the 4239 // "symbol" UTF8 string below 4240 // const char *symbol = ""; 4241 // if (row.expanded) 4242 // symbol = "\xe2\x96\xbd "; 4243 // else 4244 // symbol = "\xe2\x96\xb7 "; 4245 // window.PutCString (symbol); 4246 4247 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v' 4248 // or '>' character... 4249 // if (expanded) 4250 // window.PutChar (ACS_DARROW); 4251 // else 4252 // window.PutChar (ACS_RARROW); 4253 // Since we can't find any good looking right arrow/down arrow symbols, 4254 // just use a diamond... 4255 window.PutChar(ACS_DIAMOND); 4256 window.PutChar(ACS_HLINE); 4257 } 4258 } 4259 4260 void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) { 4261 if (parent) 4262 parent->DrawTreeForChild(window, this, reverse_depth + 1); 4263 4264 if (&GetChildren().back() == child) { 4265 // Last child 4266 if (reverse_depth == 0) { 4267 window.PutChar(ACS_LLCORNER); 4268 window.PutChar(ACS_HLINE); 4269 } else { 4270 window.PutChar(' '); 4271 window.PutChar(' '); 4272 } 4273 } else { 4274 if (reverse_depth == 0) { 4275 window.PutChar(ACS_LTEE); 4276 window.PutChar(ACS_HLINE); 4277 } else { 4278 window.PutChar(ACS_VLINE); 4279 window.PutChar(' '); 4280 } 4281 } 4282 } 4283 }; 4284 4285 struct DisplayOptions { 4286 bool show_types; 4287 }; 4288 4289 class TreeItem; 4290 4291 class TreeDelegate { 4292 public: 4293 TreeDelegate() = default; 4294 virtual ~TreeDelegate() = default; 4295 4296 virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0; 4297 virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0; 4298 virtual void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index, 4299 TreeItem *&selected_item) { 4300 return; 4301 } 4302 // This is invoked when a tree item is selected. If true is returned, the 4303 // views are updated. 4304 virtual bool TreeDelegateItemSelected(TreeItem &item) = 0; 4305 virtual bool TreeDelegateExpandRootByDefault() { return false; } 4306 // This is mostly useful for root tree delegates. If false is returned, 4307 // drawing will be skipped completely. This is needed, for instance, in 4308 // skipping drawing of the threads tree if there is no running process. 4309 virtual bool TreeDelegateShouldDraw() { return true; } 4310 }; 4311 4312 typedef std::shared_ptr<TreeDelegate> TreeDelegateSP; 4313 4314 class TreeItem { 4315 public: 4316 TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children) 4317 : m_parent(parent), m_delegate(delegate), m_user_data(nullptr), 4318 m_identifier(0), m_row_idx(-1), m_children(), 4319 m_might_have_children(might_have_children), m_is_expanded(false) { 4320 if (m_parent == nullptr) 4321 m_is_expanded = m_delegate.TreeDelegateExpandRootByDefault(); 4322 } 4323 4324 TreeItem &operator=(const TreeItem &rhs) { 4325 if (this != &rhs) { 4326 m_parent = rhs.m_parent; 4327 m_delegate = rhs.m_delegate; 4328 m_user_data = rhs.m_user_data; 4329 m_identifier = rhs.m_identifier; 4330 m_row_idx = rhs.m_row_idx; 4331 m_children = rhs.m_children; 4332 m_might_have_children = rhs.m_might_have_children; 4333 m_is_expanded = rhs.m_is_expanded; 4334 } 4335 return *this; 4336 } 4337 4338 TreeItem(const TreeItem &) = default; 4339 4340 size_t GetDepth() const { 4341 if (m_parent) 4342 return 1 + m_parent->GetDepth(); 4343 return 0; 4344 } 4345 4346 int GetRowIndex() const { return m_row_idx; } 4347 4348 void ClearChildren() { m_children.clear(); } 4349 4350 void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); } 4351 4352 TreeItem &operator[](size_t i) { return m_children[i]; } 4353 4354 void SetRowIndex(int row_idx) { m_row_idx = row_idx; } 4355 4356 size_t GetNumChildren() { 4357 m_delegate.TreeDelegateGenerateChildren(*this); 4358 return m_children.size(); 4359 } 4360 4361 void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); } 4362 4363 void CalculateRowIndexes(int &row_idx) { 4364 SetRowIndex(row_idx); 4365 ++row_idx; 4366 4367 const bool expanded = IsExpanded(); 4368 4369 // The root item must calculate its children, or we must calculate the 4370 // number of children if the item is expanded 4371 if (m_parent == nullptr || expanded) 4372 GetNumChildren(); 4373 4374 for (auto &item : m_children) { 4375 if (expanded) 4376 item.CalculateRowIndexes(row_idx); 4377 else 4378 item.SetRowIndex(-1); 4379 } 4380 } 4381 4382 TreeItem *GetParent() { return m_parent; } 4383 4384 bool IsExpanded() const { return m_is_expanded; } 4385 4386 void Expand() { m_is_expanded = true; } 4387 4388 void Unexpand() { m_is_expanded = false; } 4389 4390 bool Draw(Window &window, const int first_visible_row, 4391 const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) { 4392 if (num_rows_left <= 0) 4393 return false; 4394 4395 if (m_row_idx >= first_visible_row) { 4396 window.MoveCursor(2, row_idx + 1); 4397 4398 if (m_parent) 4399 m_parent->DrawTreeForChild(window, this, 0); 4400 4401 if (m_might_have_children) { 4402 // It we can get UTF8 characters to work we should try to use the 4403 // "symbol" UTF8 string below 4404 // const char *symbol = ""; 4405 // if (row.expanded) 4406 // symbol = "\xe2\x96\xbd "; 4407 // else 4408 // symbol = "\xe2\x96\xb7 "; 4409 // window.PutCString (symbol); 4410 4411 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 4412 // 'v' or '>' character... 4413 // if (expanded) 4414 // window.PutChar (ACS_DARROW); 4415 // else 4416 // window.PutChar (ACS_RARROW); 4417 // Since we can't find any good looking right arrow/down arrow symbols, 4418 // just use a diamond... 4419 window.PutChar(ACS_DIAMOND); 4420 window.PutChar(ACS_HLINE); 4421 } 4422 bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) && 4423 window.IsActive(); 4424 4425 if (highlight) 4426 window.AttributeOn(A_REVERSE); 4427 4428 m_delegate.TreeDelegateDrawTreeItem(*this, window); 4429 4430 if (highlight) 4431 window.AttributeOff(A_REVERSE); 4432 ++row_idx; 4433 --num_rows_left; 4434 } 4435 4436 if (num_rows_left <= 0) 4437 return false; // We are done drawing... 4438 4439 if (IsExpanded()) { 4440 for (auto &item : m_children) { 4441 // If we displayed all the rows and item.Draw() returns false we are 4442 // done drawing and can exit this for loop 4443 if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx, 4444 num_rows_left)) 4445 break; 4446 } 4447 } 4448 return num_rows_left >= 0; // Return true if not done drawing yet 4449 } 4450 4451 void DrawTreeForChild(Window &window, TreeItem *child, 4452 uint32_t reverse_depth) { 4453 if (m_parent) 4454 m_parent->DrawTreeForChild(window, this, reverse_depth + 1); 4455 4456 if (&m_children.back() == child) { 4457 // Last child 4458 if (reverse_depth == 0) { 4459 window.PutChar(ACS_LLCORNER); 4460 window.PutChar(ACS_HLINE); 4461 } else { 4462 window.PutChar(' '); 4463 window.PutChar(' '); 4464 } 4465 } else { 4466 if (reverse_depth == 0) { 4467 window.PutChar(ACS_LTEE); 4468 window.PutChar(ACS_HLINE); 4469 } else { 4470 window.PutChar(ACS_VLINE); 4471 window.PutChar(' '); 4472 } 4473 } 4474 } 4475 4476 TreeItem *GetItemForRowIndex(uint32_t row_idx) { 4477 if (static_cast<uint32_t>(m_row_idx) == row_idx) 4478 return this; 4479 if (m_children.empty()) 4480 return nullptr; 4481 if (IsExpanded()) { 4482 for (auto &item : m_children) { 4483 TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx); 4484 if (selected_item_ptr) 4485 return selected_item_ptr; 4486 } 4487 } 4488 return nullptr; 4489 } 4490 4491 void *GetUserData() const { return m_user_data; } 4492 4493 void SetUserData(void *user_data) { m_user_data = user_data; } 4494 4495 uint64_t GetIdentifier() const { return m_identifier; } 4496 4497 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } 4498 4499 const std::string &GetText() const { return m_text; } 4500 4501 void SetText(const char *text) { 4502 if (text == nullptr) { 4503 m_text.clear(); 4504 return; 4505 } 4506 m_text = text; 4507 } 4508 4509 void SetMightHaveChildren(bool b) { m_might_have_children = b; } 4510 4511 protected: 4512 TreeItem *m_parent; 4513 TreeDelegate &m_delegate; 4514 void *m_user_data; 4515 uint64_t m_identifier; 4516 std::string m_text; 4517 int m_row_idx; // Zero based visible row index, -1 if not visible or for the 4518 // root item 4519 std::vector<TreeItem> m_children; 4520 bool m_might_have_children; 4521 bool m_is_expanded; 4522 }; 4523 4524 class TreeWindowDelegate : public WindowDelegate { 4525 public: 4526 TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp) 4527 : m_debugger(debugger), m_delegate_sp(delegate_sp), 4528 m_root(nullptr, *delegate_sp, true), m_selected_item(nullptr), 4529 m_num_rows(0), m_selected_row_idx(0), m_first_visible_row(0), 4530 m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {} 4531 4532 int NumVisibleRows() const { return m_max_y - m_min_y; } 4533 4534 bool WindowDelegateDraw(Window &window, bool force) override { 4535 m_min_x = 2; 4536 m_min_y = 1; 4537 m_max_x = window.GetWidth() - 1; 4538 m_max_y = window.GetHeight() - 1; 4539 4540 window.Erase(); 4541 window.DrawTitleBox(window.GetName()); 4542 4543 if (!m_delegate_sp->TreeDelegateShouldDraw()) { 4544 m_selected_item = nullptr; 4545 return true; 4546 } 4547 4548 const int num_visible_rows = NumVisibleRows(); 4549 m_num_rows = 0; 4550 m_root.CalculateRowIndexes(m_num_rows); 4551 m_delegate_sp->TreeDelegateUpdateSelection(m_root, m_selected_row_idx, 4552 m_selected_item); 4553 4554 // If we unexpanded while having something selected our total number of 4555 // rows is less than the num visible rows, then make sure we show all the 4556 // rows by setting the first visible row accordingly. 4557 if (m_first_visible_row > 0 && m_num_rows < num_visible_rows) 4558 m_first_visible_row = 0; 4559 4560 // Make sure the selected row is always visible 4561 if (m_selected_row_idx < m_first_visible_row) 4562 m_first_visible_row = m_selected_row_idx; 4563 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) 4564 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; 4565 4566 int row_idx = 0; 4567 int num_rows_left = num_visible_rows; 4568 m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx, 4569 num_rows_left); 4570 // Get the selected row 4571 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 4572 4573 return true; // Drawing handled 4574 } 4575 4576 const char *WindowDelegateGetHelpText() override { 4577 return "Thread window keyboard shortcuts:"; 4578 } 4579 4580 KeyHelp *WindowDelegateGetKeyHelp() override { 4581 static curses::KeyHelp g_source_view_key_help[] = { 4582 {KEY_UP, "Select previous item"}, 4583 {KEY_DOWN, "Select next item"}, 4584 {KEY_RIGHT, "Expand the selected item"}, 4585 {KEY_LEFT, 4586 "Unexpand the selected item or select parent if not expanded"}, 4587 {KEY_PPAGE, "Page up"}, 4588 {KEY_NPAGE, "Page down"}, 4589 {'h', "Show help dialog"}, 4590 {' ', "Toggle item expansion"}, 4591 {',', "Page up"}, 4592 {'.', "Page down"}, 4593 {'\0', nullptr}}; 4594 return g_source_view_key_help; 4595 } 4596 4597 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 4598 switch (c) { 4599 case ',': 4600 case KEY_PPAGE: 4601 // Page up key 4602 if (m_first_visible_row > 0) { 4603 if (m_first_visible_row > m_max_y) 4604 m_first_visible_row -= m_max_y; 4605 else 4606 m_first_visible_row = 0; 4607 m_selected_row_idx = m_first_visible_row; 4608 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 4609 if (m_selected_item) 4610 m_selected_item->ItemWasSelected(); 4611 } 4612 return eKeyHandled; 4613 4614 case '.': 4615 case KEY_NPAGE: 4616 // Page down key 4617 if (m_num_rows > m_max_y) { 4618 if (m_first_visible_row + m_max_y < m_num_rows) { 4619 m_first_visible_row += m_max_y; 4620 m_selected_row_idx = m_first_visible_row; 4621 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 4622 if (m_selected_item) 4623 m_selected_item->ItemWasSelected(); 4624 } 4625 } 4626 return eKeyHandled; 4627 4628 case KEY_UP: 4629 if (m_selected_row_idx > 0) { 4630 --m_selected_row_idx; 4631 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 4632 if (m_selected_item) 4633 m_selected_item->ItemWasSelected(); 4634 } 4635 return eKeyHandled; 4636 4637 case KEY_DOWN: 4638 if (m_selected_row_idx + 1 < m_num_rows) { 4639 ++m_selected_row_idx; 4640 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 4641 if (m_selected_item) 4642 m_selected_item->ItemWasSelected(); 4643 } 4644 return eKeyHandled; 4645 4646 case KEY_RIGHT: 4647 if (m_selected_item) { 4648 if (!m_selected_item->IsExpanded()) 4649 m_selected_item->Expand(); 4650 } 4651 return eKeyHandled; 4652 4653 case KEY_LEFT: 4654 if (m_selected_item) { 4655 if (m_selected_item->IsExpanded()) 4656 m_selected_item->Unexpand(); 4657 else if (m_selected_item->GetParent()) { 4658 m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex(); 4659 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 4660 if (m_selected_item) 4661 m_selected_item->ItemWasSelected(); 4662 } 4663 } 4664 return eKeyHandled; 4665 4666 case ' ': 4667 // Toggle expansion state when SPACE is pressed 4668 if (m_selected_item) { 4669 if (m_selected_item->IsExpanded()) 4670 m_selected_item->Unexpand(); 4671 else 4672 m_selected_item->Expand(); 4673 } 4674 return eKeyHandled; 4675 4676 case 'h': 4677 window.CreateHelpSubwindow(); 4678 return eKeyHandled; 4679 4680 default: 4681 break; 4682 } 4683 return eKeyNotHandled; 4684 } 4685 4686 protected: 4687 Debugger &m_debugger; 4688 TreeDelegateSP m_delegate_sp; 4689 TreeItem m_root; 4690 TreeItem *m_selected_item; 4691 int m_num_rows; 4692 int m_selected_row_idx; 4693 int m_first_visible_row; 4694 int m_min_x; 4695 int m_min_y; 4696 int m_max_x; 4697 int m_max_y; 4698 }; 4699 4700 // A tree delegate that just draws the text member of the tree item, it doesn't 4701 // have any children or actions. 4702 class TextTreeDelegate : public TreeDelegate { 4703 public: 4704 TextTreeDelegate() : TreeDelegate() {} 4705 4706 ~TextTreeDelegate() override = default; 4707 4708 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 4709 window.PutCStringTruncated(1, item.GetText().c_str()); 4710 } 4711 4712 void TreeDelegateGenerateChildren(TreeItem &item) override {} 4713 4714 bool TreeDelegateItemSelected(TreeItem &item) override { return false; } 4715 }; 4716 4717 class FrameTreeDelegate : public TreeDelegate { 4718 public: 4719 FrameTreeDelegate() : TreeDelegate() { 4720 FormatEntity::Parse( 4721 "frame #${frame.index}: {${function.name}${function.pc-offset}}}", 4722 m_format); 4723 } 4724 4725 ~FrameTreeDelegate() override = default; 4726 4727 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 4728 Thread *thread = (Thread *)item.GetUserData(); 4729 if (thread) { 4730 const uint64_t frame_idx = item.GetIdentifier(); 4731 StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx); 4732 if (frame_sp) { 4733 StreamString strm; 4734 const SymbolContext &sc = 4735 frame_sp->GetSymbolContext(eSymbolContextEverything); 4736 ExecutionContext exe_ctx(frame_sp); 4737 if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr, 4738 nullptr, false, false)) { 4739 int right_pad = 1; 4740 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 4741 } 4742 } 4743 } 4744 } 4745 4746 void TreeDelegateGenerateChildren(TreeItem &item) override { 4747 // No children for frames yet... 4748 } 4749 4750 bool TreeDelegateItemSelected(TreeItem &item) override { 4751 Thread *thread = (Thread *)item.GetUserData(); 4752 if (thread) { 4753 thread->GetProcess()->GetThreadList().SetSelectedThreadByID( 4754 thread->GetID()); 4755 const uint64_t frame_idx = item.GetIdentifier(); 4756 thread->SetSelectedFrameByIndex(frame_idx); 4757 return true; 4758 } 4759 return false; 4760 } 4761 4762 protected: 4763 FormatEntity::Entry m_format; 4764 }; 4765 4766 class ThreadTreeDelegate : public TreeDelegate { 4767 public: 4768 ThreadTreeDelegate(Debugger &debugger) 4769 : TreeDelegate(), m_debugger(debugger), m_tid(LLDB_INVALID_THREAD_ID), 4770 m_stop_id(UINT32_MAX) { 4771 FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop " 4772 "reason = ${thread.stop-reason}}", 4773 m_format); 4774 } 4775 4776 ~ThreadTreeDelegate() override = default; 4777 4778 ProcessSP GetProcess() { 4779 return m_debugger.GetCommandInterpreter() 4780 .GetExecutionContext() 4781 .GetProcessSP(); 4782 } 4783 4784 ThreadSP GetThread(const TreeItem &item) { 4785 ProcessSP process_sp = GetProcess(); 4786 if (process_sp) 4787 return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier()); 4788 return ThreadSP(); 4789 } 4790 4791 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 4792 ThreadSP thread_sp = GetThread(item); 4793 if (thread_sp) { 4794 StreamString strm; 4795 ExecutionContext exe_ctx(thread_sp); 4796 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, 4797 nullptr, false, false)) { 4798 int right_pad = 1; 4799 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 4800 } 4801 } 4802 } 4803 4804 void TreeDelegateGenerateChildren(TreeItem &item) override { 4805 ProcessSP process_sp = GetProcess(); 4806 if (process_sp && process_sp->IsAlive()) { 4807 StateType state = process_sp->GetState(); 4808 if (StateIsStoppedState(state, true)) { 4809 ThreadSP thread_sp = GetThread(item); 4810 if (thread_sp) { 4811 if (m_stop_id == process_sp->GetStopID() && 4812 thread_sp->GetID() == m_tid) 4813 return; // Children are already up to date 4814 if (!m_frame_delegate_sp) { 4815 // Always expand the thread item the first time we show it 4816 m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>(); 4817 } 4818 4819 m_stop_id = process_sp->GetStopID(); 4820 m_tid = thread_sp->GetID(); 4821 4822 TreeItem t(&item, *m_frame_delegate_sp, false); 4823 size_t num_frames = thread_sp->GetStackFrameCount(); 4824 item.Resize(num_frames, t); 4825 for (size_t i = 0; i < num_frames; ++i) { 4826 item[i].SetUserData(thread_sp.get()); 4827 item[i].SetIdentifier(i); 4828 } 4829 } 4830 return; 4831 } 4832 } 4833 item.ClearChildren(); 4834 } 4835 4836 bool TreeDelegateItemSelected(TreeItem &item) override { 4837 ProcessSP process_sp = GetProcess(); 4838 if (process_sp && process_sp->IsAlive()) { 4839 StateType state = process_sp->GetState(); 4840 if (StateIsStoppedState(state, true)) { 4841 ThreadSP thread_sp = GetThread(item); 4842 if (thread_sp) { 4843 ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList(); 4844 std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex()); 4845 ThreadSP selected_thread_sp = thread_list.GetSelectedThread(); 4846 if (selected_thread_sp->GetID() != thread_sp->GetID()) { 4847 thread_list.SetSelectedThreadByID(thread_sp->GetID()); 4848 return true; 4849 } 4850 } 4851 } 4852 } 4853 return false; 4854 } 4855 4856 protected: 4857 Debugger &m_debugger; 4858 std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp; 4859 lldb::user_id_t m_tid; 4860 uint32_t m_stop_id; 4861 FormatEntity::Entry m_format; 4862 }; 4863 4864 class ThreadsTreeDelegate : public TreeDelegate { 4865 public: 4866 ThreadsTreeDelegate(Debugger &debugger) 4867 : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger), 4868 m_stop_id(UINT32_MAX), m_update_selection(false) { 4869 FormatEntity::Parse("process ${process.id}{, name = ${process.name}}", 4870 m_format); 4871 } 4872 4873 ~ThreadsTreeDelegate() override = default; 4874 4875 ProcessSP GetProcess() { 4876 return m_debugger.GetCommandInterpreter() 4877 .GetExecutionContext() 4878 .GetProcessSP(); 4879 } 4880 4881 bool TreeDelegateShouldDraw() override { 4882 ProcessSP process = GetProcess(); 4883 if (!process) 4884 return false; 4885 4886 if (StateIsRunningState(process->GetState())) 4887 return false; 4888 4889 return true; 4890 } 4891 4892 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 4893 ProcessSP process_sp = GetProcess(); 4894 if (process_sp && process_sp->IsAlive()) { 4895 StreamString strm; 4896 ExecutionContext exe_ctx(process_sp); 4897 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, 4898 nullptr, false, false)) { 4899 int right_pad = 1; 4900 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 4901 } 4902 } 4903 } 4904 4905 void TreeDelegateGenerateChildren(TreeItem &item) override { 4906 ProcessSP process_sp = GetProcess(); 4907 m_update_selection = false; 4908 if (process_sp && process_sp->IsAlive()) { 4909 StateType state = process_sp->GetState(); 4910 if (StateIsStoppedState(state, true)) { 4911 const uint32_t stop_id = process_sp->GetStopID(); 4912 if (m_stop_id == stop_id) 4913 return; // Children are already up to date 4914 4915 m_stop_id = stop_id; 4916 m_update_selection = true; 4917 4918 if (!m_thread_delegate_sp) { 4919 // Always expand the thread item the first time we show it 4920 // item.Expand(); 4921 m_thread_delegate_sp = 4922 std::make_shared<ThreadTreeDelegate>(m_debugger); 4923 } 4924 4925 TreeItem t(&item, *m_thread_delegate_sp, false); 4926 ThreadList &threads = process_sp->GetThreadList(); 4927 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 4928 ThreadSP selected_thread = threads.GetSelectedThread(); 4929 size_t num_threads = threads.GetSize(); 4930 item.Resize(num_threads, t); 4931 for (size_t i = 0; i < num_threads; ++i) { 4932 ThreadSP thread = threads.GetThreadAtIndex(i); 4933 item[i].SetIdentifier(thread->GetID()); 4934 item[i].SetMightHaveChildren(true); 4935 if (selected_thread->GetID() == thread->GetID()) 4936 item[i].Expand(); 4937 } 4938 return; 4939 } 4940 } 4941 item.ClearChildren(); 4942 } 4943 4944 void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index, 4945 TreeItem *&selected_item) override { 4946 if (!m_update_selection) 4947 return; 4948 4949 ProcessSP process_sp = GetProcess(); 4950 if (!(process_sp && process_sp->IsAlive())) 4951 return; 4952 4953 StateType state = process_sp->GetState(); 4954 if (!StateIsStoppedState(state, true)) 4955 return; 4956 4957 ThreadList &threads = process_sp->GetThreadList(); 4958 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 4959 ThreadSP selected_thread = threads.GetSelectedThread(); 4960 size_t num_threads = threads.GetSize(); 4961 for (size_t i = 0; i < num_threads; ++i) { 4962 ThreadSP thread = threads.GetThreadAtIndex(i); 4963 if (selected_thread->GetID() == thread->GetID()) { 4964 selected_item = &root[i][thread->GetSelectedFrameIndex()]; 4965 selection_index = selected_item->GetRowIndex(); 4966 return; 4967 } 4968 } 4969 } 4970 4971 bool TreeDelegateItemSelected(TreeItem &item) override { return false; } 4972 4973 bool TreeDelegateExpandRootByDefault() override { return true; } 4974 4975 protected: 4976 std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp; 4977 Debugger &m_debugger; 4978 uint32_t m_stop_id; 4979 bool m_update_selection; 4980 FormatEntity::Entry m_format; 4981 }; 4982 4983 class BreakpointLocationTreeDelegate : public TreeDelegate { 4984 public: 4985 BreakpointLocationTreeDelegate(Debugger &debugger) 4986 : TreeDelegate(), m_debugger(debugger) {} 4987 4988 ~BreakpointLocationTreeDelegate() override = default; 4989 4990 Process *GetProcess() { 4991 ExecutionContext exe_ctx( 4992 m_debugger.GetCommandInterpreter().GetExecutionContext()); 4993 return exe_ctx.GetProcessPtr(); 4994 } 4995 4996 BreakpointLocationSP GetBreakpointLocation(const TreeItem &item) { 4997 Breakpoint *breakpoint = (Breakpoint *)item.GetUserData(); 4998 return breakpoint->GetLocationAtIndex(item.GetIdentifier()); 4999 } 5000 5001 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 5002 BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item); 5003 Process *process = GetProcess(); 5004 StreamString stream; 5005 stream.Printf("%i.%i: ", breakpoint_location->GetBreakpoint().GetID(), 5006 breakpoint_location->GetID()); 5007 Address address = breakpoint_location->GetAddress(); 5008 address.Dump(&stream, process, Address::DumpStyleResolvedDescription, 5009 Address::DumpStyleInvalid); 5010 window.PutCStringTruncated(1, stream.GetString().str().c_str()); 5011 } 5012 5013 StringList ComputeDetailsList(BreakpointLocationSP breakpoint_location) { 5014 StringList details; 5015 5016 Address address = breakpoint_location->GetAddress(); 5017 SymbolContext symbol_context; 5018 address.CalculateSymbolContext(&symbol_context); 5019 5020 if (symbol_context.module_sp) { 5021 StreamString module_stream; 5022 module_stream.PutCString("module = "); 5023 symbol_context.module_sp->GetFileSpec().Dump( 5024 module_stream.AsRawOstream()); 5025 details.AppendString(module_stream.GetString()); 5026 } 5027 5028 if (symbol_context.comp_unit != nullptr) { 5029 StreamString compile_unit_stream; 5030 compile_unit_stream.PutCString("compile unit = "); 5031 symbol_context.comp_unit->GetPrimaryFile().GetFilename().Dump( 5032 &compile_unit_stream); 5033 details.AppendString(compile_unit_stream.GetString()); 5034 5035 if (symbol_context.function != nullptr) { 5036 StreamString function_stream; 5037 function_stream.PutCString("function = "); 5038 function_stream.PutCString( 5039 symbol_context.function->GetName().AsCString("<unknown>")); 5040 details.AppendString(function_stream.GetString()); 5041 } 5042 5043 if (symbol_context.line_entry.line > 0) { 5044 StreamString location_stream; 5045 location_stream.PutCString("location = "); 5046 symbol_context.line_entry.DumpStopContext(&location_stream, true); 5047 details.AppendString(location_stream.GetString()); 5048 } 5049 5050 } else { 5051 if (symbol_context.symbol) { 5052 StreamString symbol_stream; 5053 if (breakpoint_location->IsReExported()) 5054 symbol_stream.PutCString("re-exported target = "); 5055 else 5056 symbol_stream.PutCString("symbol = "); 5057 symbol_stream.PutCString( 5058 symbol_context.symbol->GetName().AsCString("<unknown>")); 5059 details.AppendString(symbol_stream.GetString()); 5060 } 5061 } 5062 5063 Process *process = GetProcess(); 5064 5065 StreamString address_stream; 5066 address.Dump(&address_stream, process, Address::DumpStyleLoadAddress, 5067 Address::DumpStyleModuleWithFileAddress); 5068 details.AppendString(address_stream.GetString()); 5069 5070 BreakpointSiteSP breakpoint_site = breakpoint_location->GetBreakpointSite(); 5071 if (breakpoint_location->IsIndirect() && breakpoint_site) { 5072 Address resolved_address; 5073 resolved_address.SetLoadAddress(breakpoint_site->GetLoadAddress(), 5074 &breakpoint_location->GetTarget()); 5075 Symbol *resolved_symbol = resolved_address.CalculateSymbolContextSymbol(); 5076 if (resolved_symbol) { 5077 StreamString indirect_target_stream; 5078 indirect_target_stream.PutCString("indirect target = "); 5079 indirect_target_stream.PutCString( 5080 resolved_symbol->GetName().GetCString()); 5081 details.AppendString(indirect_target_stream.GetString()); 5082 } 5083 } 5084 5085 bool is_resolved = breakpoint_location->IsResolved(); 5086 StreamString resolved_stream; 5087 resolved_stream.Printf("resolved = %s", is_resolved ? "true" : "false"); 5088 details.AppendString(resolved_stream.GetString()); 5089 5090 bool is_hardware = is_resolved && breakpoint_site->IsHardware(); 5091 StreamString hardware_stream; 5092 hardware_stream.Printf("hardware = %s", is_hardware ? "true" : "false"); 5093 details.AppendString(hardware_stream.GetString()); 5094 5095 StreamString hit_count_stream; 5096 hit_count_stream.Printf("hit count = %-4u", 5097 breakpoint_location->GetHitCount()); 5098 details.AppendString(hit_count_stream.GetString()); 5099 5100 return details; 5101 } 5102 5103 void TreeDelegateGenerateChildren(TreeItem &item) override { 5104 BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item); 5105 StringList details = ComputeDetailsList(breakpoint_location); 5106 5107 if (!m_string_delegate_sp) 5108 m_string_delegate_sp = std::make_shared<TextTreeDelegate>(); 5109 TreeItem details_tree_item(&item, *m_string_delegate_sp, false); 5110 5111 item.Resize(details.GetSize(), details_tree_item); 5112 for (size_t i = 0; i < details.GetSize(); i++) { 5113 item[i].SetText(details.GetStringAtIndex(i)); 5114 } 5115 } 5116 5117 bool TreeDelegateItemSelected(TreeItem &item) override { return false; } 5118 5119 protected: 5120 Debugger &m_debugger; 5121 std::shared_ptr<TextTreeDelegate> m_string_delegate_sp; 5122 }; 5123 5124 class BreakpointTreeDelegate : public TreeDelegate { 5125 public: 5126 BreakpointTreeDelegate(Debugger &debugger) 5127 : TreeDelegate(), m_debugger(debugger), 5128 m_breakpoint_location_delegate_sp() {} 5129 5130 ~BreakpointTreeDelegate() override = default; 5131 5132 BreakpointSP GetBreakpoint(const TreeItem &item) { 5133 TargetSP target = m_debugger.GetSelectedTarget(); 5134 BreakpointList &breakpoints = target->GetBreakpointList(false); 5135 return breakpoints.GetBreakpointAtIndex(item.GetIdentifier()); 5136 } 5137 5138 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 5139 BreakpointSP breakpoint = GetBreakpoint(item); 5140 StreamString stream; 5141 stream.Format("{0}: ", breakpoint->GetID()); 5142 breakpoint->GetResolverDescription(&stream); 5143 breakpoint->GetFilterDescription(&stream); 5144 window.PutCStringTruncated(1, stream.GetString().str().c_str()); 5145 } 5146 5147 void TreeDelegateGenerateChildren(TreeItem &item) override { 5148 BreakpointSP breakpoint = GetBreakpoint(item); 5149 5150 if (!m_breakpoint_location_delegate_sp) 5151 m_breakpoint_location_delegate_sp = 5152 std::make_shared<BreakpointLocationTreeDelegate>(m_debugger); 5153 TreeItem breakpoint_location_tree_item( 5154 &item, *m_breakpoint_location_delegate_sp, true); 5155 5156 item.Resize(breakpoint->GetNumLocations(), breakpoint_location_tree_item); 5157 for (size_t i = 0; i < breakpoint->GetNumLocations(); i++) { 5158 item[i].SetIdentifier(i); 5159 item[i].SetUserData(breakpoint.get()); 5160 } 5161 } 5162 5163 bool TreeDelegateItemSelected(TreeItem &item) override { return false; } 5164 5165 protected: 5166 Debugger &m_debugger; 5167 std::shared_ptr<BreakpointLocationTreeDelegate> 5168 m_breakpoint_location_delegate_sp; 5169 }; 5170 5171 class BreakpointsTreeDelegate : public TreeDelegate { 5172 public: 5173 BreakpointsTreeDelegate(Debugger &debugger) 5174 : TreeDelegate(), m_debugger(debugger), m_breakpoint_delegate_sp() {} 5175 5176 ~BreakpointsTreeDelegate() override = default; 5177 5178 bool TreeDelegateShouldDraw() override { 5179 TargetSP target = m_debugger.GetSelectedTarget(); 5180 if (!target) 5181 return false; 5182 5183 return true; 5184 } 5185 5186 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 5187 window.PutCString("Breakpoints"); 5188 } 5189 5190 void TreeDelegateGenerateChildren(TreeItem &item) override { 5191 TargetSP target = m_debugger.GetSelectedTarget(); 5192 5193 BreakpointList &breakpoints = target->GetBreakpointList(false); 5194 std::unique_lock<std::recursive_mutex> lock; 5195 breakpoints.GetListMutex(lock); 5196 5197 if (!m_breakpoint_delegate_sp) 5198 m_breakpoint_delegate_sp = 5199 std::make_shared<BreakpointTreeDelegate>(m_debugger); 5200 TreeItem breakpoint_tree_item(&item, *m_breakpoint_delegate_sp, true); 5201 5202 item.Resize(breakpoints.GetSize(), breakpoint_tree_item); 5203 for (size_t i = 0; i < breakpoints.GetSize(); i++) { 5204 item[i].SetIdentifier(i); 5205 } 5206 } 5207 5208 bool TreeDelegateItemSelected(TreeItem &item) override { return false; } 5209 5210 bool TreeDelegateExpandRootByDefault() override { return true; } 5211 5212 protected: 5213 Debugger &m_debugger; 5214 std::shared_ptr<BreakpointTreeDelegate> m_breakpoint_delegate_sp; 5215 }; 5216 5217 class ValueObjectListDelegate : public WindowDelegate { 5218 public: 5219 ValueObjectListDelegate() : m_rows() {} 5220 5221 ValueObjectListDelegate(ValueObjectList &valobj_list) 5222 : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0), 5223 m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) { 5224 SetValues(valobj_list); 5225 } 5226 5227 ~ValueObjectListDelegate() override = default; 5228 5229 void SetValues(ValueObjectList &valobj_list) { 5230 m_selected_row = nullptr; 5231 m_selected_row_idx = 0; 5232 m_first_visible_row = 0; 5233 m_num_rows = 0; 5234 m_rows.clear(); 5235 for (auto &valobj_sp : valobj_list.GetObjects()) 5236 m_rows.push_back(Row(valobj_sp, nullptr)); 5237 } 5238 5239 bool WindowDelegateDraw(Window &window, bool force) override { 5240 m_num_rows = 0; 5241 m_min_x = 2; 5242 m_min_y = 1; 5243 m_max_x = window.GetWidth() - 1; 5244 m_max_y = window.GetHeight() - 1; 5245 5246 window.Erase(); 5247 window.DrawTitleBox(window.GetName()); 5248 5249 const int num_visible_rows = NumVisibleRows(); 5250 const int num_rows = CalculateTotalNumberRows(m_rows); 5251 5252 // If we unexpanded while having something selected our total number of 5253 // rows is less than the num visible rows, then make sure we show all the 5254 // rows by setting the first visible row accordingly. 5255 if (m_first_visible_row > 0 && num_rows < num_visible_rows) 5256 m_first_visible_row = 0; 5257 5258 // Make sure the selected row is always visible 5259 if (m_selected_row_idx < m_first_visible_row) 5260 m_first_visible_row = m_selected_row_idx; 5261 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) 5262 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; 5263 5264 DisplayRows(window, m_rows, g_options); 5265 5266 // Get the selected row 5267 m_selected_row = GetRowForRowIndex(m_selected_row_idx); 5268 // Keep the cursor on the selected row so the highlight and the cursor are 5269 // always on the same line 5270 if (m_selected_row) 5271 window.MoveCursor(m_selected_row->x, m_selected_row->y); 5272 5273 return true; // Drawing handled 5274 } 5275 5276 KeyHelp *WindowDelegateGetKeyHelp() override { 5277 static curses::KeyHelp g_source_view_key_help[] = { 5278 {KEY_UP, "Select previous item"}, 5279 {KEY_DOWN, "Select next item"}, 5280 {KEY_RIGHT, "Expand selected item"}, 5281 {KEY_LEFT, "Unexpand selected item or select parent if not expanded"}, 5282 {KEY_PPAGE, "Page up"}, 5283 {KEY_NPAGE, "Page down"}, 5284 {'A', "Format as annotated address"}, 5285 {'b', "Format as binary"}, 5286 {'B', "Format as hex bytes with ASCII"}, 5287 {'c', "Format as character"}, 5288 {'d', "Format as a signed integer"}, 5289 {'D', "Format selected value using the default format for the type"}, 5290 {'f', "Format as float"}, 5291 {'h', "Show help dialog"}, 5292 {'i', "Format as instructions"}, 5293 {'o', "Format as octal"}, 5294 {'p', "Format as pointer"}, 5295 {'s', "Format as C string"}, 5296 {'t', "Toggle showing/hiding type names"}, 5297 {'u', "Format as an unsigned integer"}, 5298 {'x', "Format as hex"}, 5299 {'X', "Format as uppercase hex"}, 5300 {' ', "Toggle item expansion"}, 5301 {',', "Page up"}, 5302 {'.', "Page down"}, 5303 {'\0', nullptr}}; 5304 return g_source_view_key_help; 5305 } 5306 5307 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 5308 switch (c) { 5309 case 'x': 5310 case 'X': 5311 case 'o': 5312 case 's': 5313 case 'u': 5314 case 'd': 5315 case 'D': 5316 case 'i': 5317 case 'A': 5318 case 'p': 5319 case 'c': 5320 case 'b': 5321 case 'B': 5322 case 'f': 5323 // Change the format for the currently selected item 5324 if (m_selected_row) { 5325 auto valobj_sp = m_selected_row->value.GetSP(); 5326 if (valobj_sp) 5327 valobj_sp->SetFormat(FormatForChar(c)); 5328 } 5329 return eKeyHandled; 5330 5331 case 't': 5332 // Toggle showing type names 5333 g_options.show_types = !g_options.show_types; 5334 return eKeyHandled; 5335 5336 case ',': 5337 case KEY_PPAGE: 5338 // Page up key 5339 if (m_first_visible_row > 0) { 5340 if (static_cast<int>(m_first_visible_row) > m_max_y) 5341 m_first_visible_row -= m_max_y; 5342 else 5343 m_first_visible_row = 0; 5344 m_selected_row_idx = m_first_visible_row; 5345 } 5346 return eKeyHandled; 5347 5348 case '.': 5349 case KEY_NPAGE: 5350 // Page down key 5351 if (m_num_rows > static_cast<size_t>(m_max_y)) { 5352 if (m_first_visible_row + m_max_y < m_num_rows) { 5353 m_first_visible_row += m_max_y; 5354 m_selected_row_idx = m_first_visible_row; 5355 } 5356 } 5357 return eKeyHandled; 5358 5359 case KEY_UP: 5360 if (m_selected_row_idx > 0) 5361 --m_selected_row_idx; 5362 return eKeyHandled; 5363 5364 case KEY_DOWN: 5365 if (m_selected_row_idx + 1 < m_num_rows) 5366 ++m_selected_row_idx; 5367 return eKeyHandled; 5368 5369 case KEY_RIGHT: 5370 if (m_selected_row) { 5371 if (!m_selected_row->expanded) 5372 m_selected_row->Expand(); 5373 } 5374 return eKeyHandled; 5375 5376 case KEY_LEFT: 5377 if (m_selected_row) { 5378 if (m_selected_row->expanded) 5379 m_selected_row->Unexpand(); 5380 else if (m_selected_row->parent) 5381 m_selected_row_idx = m_selected_row->parent->row_idx; 5382 } 5383 return eKeyHandled; 5384 5385 case ' ': 5386 // Toggle expansion state when SPACE is pressed 5387 if (m_selected_row) { 5388 if (m_selected_row->expanded) 5389 m_selected_row->Unexpand(); 5390 else 5391 m_selected_row->Expand(); 5392 } 5393 return eKeyHandled; 5394 5395 case 'h': 5396 window.CreateHelpSubwindow(); 5397 return eKeyHandled; 5398 5399 default: 5400 break; 5401 } 5402 return eKeyNotHandled; 5403 } 5404 5405 protected: 5406 std::vector<Row> m_rows; 5407 Row *m_selected_row = nullptr; 5408 uint32_t m_selected_row_idx = 0; 5409 uint32_t m_first_visible_row = 0; 5410 uint32_t m_num_rows = 0; 5411 int m_min_x; 5412 int m_min_y; 5413 int m_max_x = 0; 5414 int m_max_y = 0; 5415 5416 static Format FormatForChar(int c) { 5417 switch (c) { 5418 case 'x': 5419 return eFormatHex; 5420 case 'X': 5421 return eFormatHexUppercase; 5422 case 'o': 5423 return eFormatOctal; 5424 case 's': 5425 return eFormatCString; 5426 case 'u': 5427 return eFormatUnsigned; 5428 case 'd': 5429 return eFormatDecimal; 5430 case 'D': 5431 return eFormatDefault; 5432 case 'i': 5433 return eFormatInstruction; 5434 case 'A': 5435 return eFormatAddressInfo; 5436 case 'p': 5437 return eFormatPointer; 5438 case 'c': 5439 return eFormatChar; 5440 case 'b': 5441 return eFormatBinary; 5442 case 'B': 5443 return eFormatBytesWithASCII; 5444 case 'f': 5445 return eFormatFloat; 5446 } 5447 return eFormatDefault; 5448 } 5449 5450 bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options, 5451 bool highlight, bool last_child) { 5452 ValueObject *valobj = row.value.GetSP().get(); 5453 5454 if (valobj == nullptr) 5455 return false; 5456 5457 const char *type_name = 5458 options.show_types ? valobj->GetTypeName().GetCString() : nullptr; 5459 const char *name = valobj->GetName().GetCString(); 5460 const char *value = valobj->GetValueAsCString(); 5461 const char *summary = valobj->GetSummaryAsCString(); 5462 5463 window.MoveCursor(row.x, row.y); 5464 5465 row.DrawTree(window); 5466 5467 if (highlight) 5468 window.AttributeOn(A_REVERSE); 5469 5470 if (type_name && type_name[0]) 5471 window.PrintfTruncated(1, "(%s) ", type_name); 5472 5473 if (name && name[0]) 5474 window.PutCStringTruncated(1, name); 5475 5476 attr_t changd_attr = 0; 5477 if (valobj->GetValueDidChange()) 5478 changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD; 5479 5480 if (value && value[0]) { 5481 window.PutCStringTruncated(1, " = "); 5482 if (changd_attr) 5483 window.AttributeOn(changd_attr); 5484 window.PutCStringTruncated(1, value); 5485 if (changd_attr) 5486 window.AttributeOff(changd_attr); 5487 } 5488 5489 if (summary && summary[0]) { 5490 window.PutCStringTruncated(1, " "); 5491 if (changd_attr) 5492 window.AttributeOn(changd_attr); 5493 window.PutCStringTruncated(1, summary); 5494 if (changd_attr) 5495 window.AttributeOff(changd_attr); 5496 } 5497 5498 if (highlight) 5499 window.AttributeOff(A_REVERSE); 5500 5501 return true; 5502 } 5503 5504 void DisplayRows(Window &window, std::vector<Row> &rows, 5505 DisplayOptions &options) { 5506 // > 0x25B7 5507 // \/ 0x25BD 5508 5509 bool window_is_active = window.IsActive(); 5510 for (auto &row : rows) { 5511 const bool last_child = row.parent && &rows[rows.size() - 1] == &row; 5512 // Save the row index in each Row structure 5513 row.row_idx = m_num_rows; 5514 if ((m_num_rows >= m_first_visible_row) && 5515 ((m_num_rows - m_first_visible_row) < 5516 static_cast<size_t>(NumVisibleRows()))) { 5517 row.x = m_min_x; 5518 row.y = m_num_rows - m_first_visible_row + 1; 5519 if (DisplayRowObject(window, row, options, 5520 window_is_active && 5521 m_num_rows == m_selected_row_idx, 5522 last_child)) { 5523 ++m_num_rows; 5524 } else { 5525 row.x = 0; 5526 row.y = 0; 5527 } 5528 } else { 5529 row.x = 0; 5530 row.y = 0; 5531 ++m_num_rows; 5532 } 5533 5534 auto &children = row.GetChildren(); 5535 if (row.expanded && !children.empty()) { 5536 DisplayRows(window, children, options); 5537 } 5538 } 5539 } 5540 5541 int CalculateTotalNumberRows(std::vector<Row> &rows) { 5542 int row_count = 0; 5543 for (auto &row : rows) { 5544 ++row_count; 5545 if (row.expanded) 5546 row_count += CalculateTotalNumberRows(row.GetChildren()); 5547 } 5548 return row_count; 5549 } 5550 5551 static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) { 5552 for (auto &row : rows) { 5553 if (row_index == 0) 5554 return &row; 5555 else { 5556 --row_index; 5557 auto &children = row.GetChildren(); 5558 if (row.expanded && !children.empty()) { 5559 Row *result = GetRowForRowIndexImpl(children, row_index); 5560 if (result) 5561 return result; 5562 } 5563 } 5564 } 5565 return nullptr; 5566 } 5567 5568 Row *GetRowForRowIndex(size_t row_index) { 5569 return GetRowForRowIndexImpl(m_rows, row_index); 5570 } 5571 5572 int NumVisibleRows() const { return m_max_y - m_min_y; } 5573 5574 static DisplayOptions g_options; 5575 }; 5576 5577 class FrameVariablesWindowDelegate : public ValueObjectListDelegate { 5578 public: 5579 FrameVariablesWindowDelegate(Debugger &debugger) 5580 : ValueObjectListDelegate(), m_debugger(debugger), 5581 m_frame_block(nullptr) {} 5582 5583 ~FrameVariablesWindowDelegate() override = default; 5584 5585 const char *WindowDelegateGetHelpText() override { 5586 return "Frame variable window keyboard shortcuts:"; 5587 } 5588 5589 bool WindowDelegateDraw(Window &window, bool force) override { 5590 ExecutionContext exe_ctx( 5591 m_debugger.GetCommandInterpreter().GetExecutionContext()); 5592 Process *process = exe_ctx.GetProcessPtr(); 5593 Block *frame_block = nullptr; 5594 StackFrame *frame = nullptr; 5595 5596 if (process) { 5597 StateType state = process->GetState(); 5598 if (StateIsStoppedState(state, true)) { 5599 frame = exe_ctx.GetFramePtr(); 5600 if (frame) 5601 frame_block = frame->GetFrameBlock(); 5602 } else if (StateIsRunningState(state)) { 5603 return true; // Don't do any updating when we are running 5604 } 5605 } 5606 5607 ValueObjectList local_values; 5608 if (frame_block) { 5609 // Only update the variables if they have changed 5610 if (m_frame_block != frame_block) { 5611 m_frame_block = frame_block; 5612 5613 VariableList *locals = frame->GetVariableList(true); 5614 if (locals) { 5615 const DynamicValueType use_dynamic = eDynamicDontRunTarget; 5616 for (const VariableSP &local_sp : *locals) { 5617 ValueObjectSP value_sp = 5618 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic); 5619 if (value_sp) { 5620 ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue(); 5621 if (synthetic_value_sp) 5622 local_values.Append(synthetic_value_sp); 5623 else 5624 local_values.Append(value_sp); 5625 } 5626 } 5627 // Update the values 5628 SetValues(local_values); 5629 } 5630 } 5631 } else { 5632 m_frame_block = nullptr; 5633 // Update the values with an empty list if there is no frame 5634 SetValues(local_values); 5635 } 5636 5637 return ValueObjectListDelegate::WindowDelegateDraw(window, force); 5638 } 5639 5640 protected: 5641 Debugger &m_debugger; 5642 Block *m_frame_block; 5643 }; 5644 5645 class RegistersWindowDelegate : public ValueObjectListDelegate { 5646 public: 5647 RegistersWindowDelegate(Debugger &debugger) 5648 : ValueObjectListDelegate(), m_debugger(debugger) {} 5649 5650 ~RegistersWindowDelegate() override = default; 5651 5652 const char *WindowDelegateGetHelpText() override { 5653 return "Register window keyboard shortcuts:"; 5654 } 5655 5656 bool WindowDelegateDraw(Window &window, bool force) override { 5657 ExecutionContext exe_ctx( 5658 m_debugger.GetCommandInterpreter().GetExecutionContext()); 5659 StackFrame *frame = exe_ctx.GetFramePtr(); 5660 5661 ValueObjectList value_list; 5662 if (frame) { 5663 if (frame->GetStackID() != m_stack_id) { 5664 m_stack_id = frame->GetStackID(); 5665 RegisterContextSP reg_ctx(frame->GetRegisterContext()); 5666 if (reg_ctx) { 5667 const uint32_t num_sets = reg_ctx->GetRegisterSetCount(); 5668 for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) { 5669 value_list.Append( 5670 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx)); 5671 } 5672 } 5673 SetValues(value_list); 5674 } 5675 } else { 5676 Process *process = exe_ctx.GetProcessPtr(); 5677 if (process && process->IsAlive()) 5678 return true; // Don't do any updating if we are running 5679 else { 5680 // Update the values with an empty list if there is no process or the 5681 // process isn't alive anymore 5682 SetValues(value_list); 5683 } 5684 } 5685 return ValueObjectListDelegate::WindowDelegateDraw(window, force); 5686 } 5687 5688 protected: 5689 Debugger &m_debugger; 5690 StackID m_stack_id; 5691 }; 5692 5693 static const char *CursesKeyToCString(int ch) { 5694 static char g_desc[32]; 5695 if (ch >= KEY_F0 && ch < KEY_F0 + 64) { 5696 snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0); 5697 return g_desc; 5698 } 5699 switch (ch) { 5700 case KEY_DOWN: 5701 return "down"; 5702 case KEY_UP: 5703 return "up"; 5704 case KEY_LEFT: 5705 return "left"; 5706 case KEY_RIGHT: 5707 return "right"; 5708 case KEY_HOME: 5709 return "home"; 5710 case KEY_BACKSPACE: 5711 return "backspace"; 5712 case KEY_DL: 5713 return "delete-line"; 5714 case KEY_IL: 5715 return "insert-line"; 5716 case KEY_DC: 5717 return "delete-char"; 5718 case KEY_IC: 5719 return "insert-char"; 5720 case KEY_CLEAR: 5721 return "clear"; 5722 case KEY_EOS: 5723 return "clear-to-eos"; 5724 case KEY_EOL: 5725 return "clear-to-eol"; 5726 case KEY_SF: 5727 return "scroll-forward"; 5728 case KEY_SR: 5729 return "scroll-backward"; 5730 case KEY_NPAGE: 5731 return "page-down"; 5732 case KEY_PPAGE: 5733 return "page-up"; 5734 case KEY_STAB: 5735 return "set-tab"; 5736 case KEY_CTAB: 5737 return "clear-tab"; 5738 case KEY_CATAB: 5739 return "clear-all-tabs"; 5740 case KEY_ENTER: 5741 return "enter"; 5742 case KEY_PRINT: 5743 return "print"; 5744 case KEY_LL: 5745 return "lower-left key"; 5746 case KEY_A1: 5747 return "upper left of keypad"; 5748 case KEY_A3: 5749 return "upper right of keypad"; 5750 case KEY_B2: 5751 return "center of keypad"; 5752 case KEY_C1: 5753 return "lower left of keypad"; 5754 case KEY_C3: 5755 return "lower right of keypad"; 5756 case KEY_BTAB: 5757 return "back-tab key"; 5758 case KEY_BEG: 5759 return "begin key"; 5760 case KEY_CANCEL: 5761 return "cancel key"; 5762 case KEY_CLOSE: 5763 return "close key"; 5764 case KEY_COMMAND: 5765 return "command key"; 5766 case KEY_COPY: 5767 return "copy key"; 5768 case KEY_CREATE: 5769 return "create key"; 5770 case KEY_END: 5771 return "end key"; 5772 case KEY_EXIT: 5773 return "exit key"; 5774 case KEY_FIND: 5775 return "find key"; 5776 case KEY_HELP: 5777 return "help key"; 5778 case KEY_MARK: 5779 return "mark key"; 5780 case KEY_MESSAGE: 5781 return "message key"; 5782 case KEY_MOVE: 5783 return "move key"; 5784 case KEY_NEXT: 5785 return "next key"; 5786 case KEY_OPEN: 5787 return "open key"; 5788 case KEY_OPTIONS: 5789 return "options key"; 5790 case KEY_PREVIOUS: 5791 return "previous key"; 5792 case KEY_REDO: 5793 return "redo key"; 5794 case KEY_REFERENCE: 5795 return "reference key"; 5796 case KEY_REFRESH: 5797 return "refresh key"; 5798 case KEY_REPLACE: 5799 return "replace key"; 5800 case KEY_RESTART: 5801 return "restart key"; 5802 case KEY_RESUME: 5803 return "resume key"; 5804 case KEY_SAVE: 5805 return "save key"; 5806 case KEY_SBEG: 5807 return "shifted begin key"; 5808 case KEY_SCANCEL: 5809 return "shifted cancel key"; 5810 case KEY_SCOMMAND: 5811 return "shifted command key"; 5812 case KEY_SCOPY: 5813 return "shifted copy key"; 5814 case KEY_SCREATE: 5815 return "shifted create key"; 5816 case KEY_SDC: 5817 return "shifted delete-character key"; 5818 case KEY_SDL: 5819 return "shifted delete-line key"; 5820 case KEY_SELECT: 5821 return "select key"; 5822 case KEY_SEND: 5823 return "shifted end key"; 5824 case KEY_SEOL: 5825 return "shifted clear-to-end-of-line key"; 5826 case KEY_SEXIT: 5827 return "shifted exit key"; 5828 case KEY_SFIND: 5829 return "shifted find key"; 5830 case KEY_SHELP: 5831 return "shifted help key"; 5832 case KEY_SHOME: 5833 return "shifted home key"; 5834 case KEY_SIC: 5835 return "shifted insert-character key"; 5836 case KEY_SLEFT: 5837 return "shifted left-arrow key"; 5838 case KEY_SMESSAGE: 5839 return "shifted message key"; 5840 case KEY_SMOVE: 5841 return "shifted move key"; 5842 case KEY_SNEXT: 5843 return "shifted next key"; 5844 case KEY_SOPTIONS: 5845 return "shifted options key"; 5846 case KEY_SPREVIOUS: 5847 return "shifted previous key"; 5848 case KEY_SPRINT: 5849 return "shifted print key"; 5850 case KEY_SREDO: 5851 return "shifted redo key"; 5852 case KEY_SREPLACE: 5853 return "shifted replace key"; 5854 case KEY_SRIGHT: 5855 return "shifted right-arrow key"; 5856 case KEY_SRSUME: 5857 return "shifted resume key"; 5858 case KEY_SSAVE: 5859 return "shifted save key"; 5860 case KEY_SSUSPEND: 5861 return "shifted suspend key"; 5862 case KEY_SUNDO: 5863 return "shifted undo key"; 5864 case KEY_SUSPEND: 5865 return "suspend key"; 5866 case KEY_UNDO: 5867 return "undo key"; 5868 case KEY_MOUSE: 5869 return "Mouse event has occurred"; 5870 case KEY_RESIZE: 5871 return "Terminal resize event"; 5872 #ifdef KEY_EVENT 5873 case KEY_EVENT: 5874 return "We were interrupted by an event"; 5875 #endif 5876 case KEY_RETURN: 5877 return "return"; 5878 case ' ': 5879 return "space"; 5880 case '\t': 5881 return "tab"; 5882 case KEY_ESCAPE: 5883 return "escape"; 5884 default: 5885 if (llvm::isPrint(ch)) 5886 snprintf(g_desc, sizeof(g_desc), "%c", ch); 5887 else 5888 snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch); 5889 return g_desc; 5890 } 5891 return nullptr; 5892 } 5893 5894 HelpDialogDelegate::HelpDialogDelegate(const char *text, 5895 KeyHelp *key_help_array) 5896 : m_text(), m_first_visible_line(0) { 5897 if (text && text[0]) { 5898 m_text.SplitIntoLines(text); 5899 m_text.AppendString(""); 5900 } 5901 if (key_help_array) { 5902 for (KeyHelp *key = key_help_array; key->ch; ++key) { 5903 StreamString key_description; 5904 key_description.Printf("%10s - %s", CursesKeyToCString(key->ch), 5905 key->description); 5906 m_text.AppendString(key_description.GetString()); 5907 } 5908 } 5909 } 5910 5911 HelpDialogDelegate::~HelpDialogDelegate() = default; 5912 5913 bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) { 5914 window.Erase(); 5915 const int window_height = window.GetHeight(); 5916 int x = 2; 5917 int y = 1; 5918 const int min_y = y; 5919 const int max_y = window_height - 1 - y; 5920 const size_t num_visible_lines = max_y - min_y + 1; 5921 const size_t num_lines = m_text.GetSize(); 5922 const char *bottom_message; 5923 if (num_lines <= num_visible_lines) 5924 bottom_message = "Press any key to exit"; 5925 else 5926 bottom_message = "Use arrows to scroll, any other key to exit"; 5927 window.DrawTitleBox(window.GetName(), bottom_message); 5928 while (y <= max_y) { 5929 window.MoveCursor(x, y); 5930 window.PutCStringTruncated( 5931 1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y)); 5932 ++y; 5933 } 5934 return true; 5935 } 5936 5937 HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window, 5938 int key) { 5939 bool done = false; 5940 const size_t num_lines = m_text.GetSize(); 5941 const size_t num_visible_lines = window.GetHeight() - 2; 5942 5943 if (num_lines <= num_visible_lines) { 5944 done = true; 5945 // If we have all lines visible and don't need scrolling, then any key 5946 // press will cause us to exit 5947 } else { 5948 switch (key) { 5949 case KEY_UP: 5950 if (m_first_visible_line > 0) 5951 --m_first_visible_line; 5952 break; 5953 5954 case KEY_DOWN: 5955 if (m_first_visible_line + num_visible_lines < num_lines) 5956 ++m_first_visible_line; 5957 break; 5958 5959 case KEY_PPAGE: 5960 case ',': 5961 if (m_first_visible_line > 0) { 5962 if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines) 5963 m_first_visible_line -= num_visible_lines; 5964 else 5965 m_first_visible_line = 0; 5966 } 5967 break; 5968 5969 case KEY_NPAGE: 5970 case '.': 5971 if (m_first_visible_line + num_visible_lines < num_lines) { 5972 m_first_visible_line += num_visible_lines; 5973 if (static_cast<size_t>(m_first_visible_line) > num_lines) 5974 m_first_visible_line = num_lines - num_visible_lines; 5975 } 5976 break; 5977 5978 default: 5979 done = true; 5980 break; 5981 } 5982 } 5983 if (done) 5984 window.GetParent()->RemoveSubWindow(&window); 5985 return eKeyHandled; 5986 } 5987 5988 class ApplicationDelegate : public WindowDelegate, public MenuDelegate { 5989 public: 5990 enum { 5991 eMenuID_LLDB = 1, 5992 eMenuID_LLDBAbout, 5993 eMenuID_LLDBExit, 5994 5995 eMenuID_Target, 5996 eMenuID_TargetCreate, 5997 eMenuID_TargetDelete, 5998 5999 eMenuID_Process, 6000 eMenuID_ProcessAttach, 6001 eMenuID_ProcessDetachResume, 6002 eMenuID_ProcessDetachSuspended, 6003 eMenuID_ProcessLaunch, 6004 eMenuID_ProcessContinue, 6005 eMenuID_ProcessHalt, 6006 eMenuID_ProcessKill, 6007 6008 eMenuID_Thread, 6009 eMenuID_ThreadStepIn, 6010 eMenuID_ThreadStepOver, 6011 eMenuID_ThreadStepOut, 6012 6013 eMenuID_View, 6014 eMenuID_ViewBacktrace, 6015 eMenuID_ViewRegisters, 6016 eMenuID_ViewSource, 6017 eMenuID_ViewVariables, 6018 eMenuID_ViewBreakpoints, 6019 6020 eMenuID_Help, 6021 eMenuID_HelpGUIHelp 6022 }; 6023 6024 ApplicationDelegate(Application &app, Debugger &debugger) 6025 : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {} 6026 6027 ~ApplicationDelegate() override = default; 6028 6029 bool WindowDelegateDraw(Window &window, bool force) override { 6030 return false; // Drawing not handled, let standard window drawing happen 6031 } 6032 6033 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { 6034 switch (key) { 6035 case '\t': 6036 window.SelectNextWindowAsActive(); 6037 return eKeyHandled; 6038 6039 case KEY_SHIFT_TAB: 6040 window.SelectPreviousWindowAsActive(); 6041 return eKeyHandled; 6042 6043 case 'h': 6044 window.CreateHelpSubwindow(); 6045 return eKeyHandled; 6046 6047 case KEY_ESCAPE: 6048 return eQuitApplication; 6049 6050 default: 6051 break; 6052 } 6053 return eKeyNotHandled; 6054 } 6055 6056 const char *WindowDelegateGetHelpText() override { 6057 return "Welcome to the LLDB curses GUI.\n\n" 6058 "Press the TAB key to change the selected view.\n" 6059 "Each view has its own keyboard shortcuts, press 'h' to open a " 6060 "dialog to display them.\n\n" 6061 "Common key bindings for all views:"; 6062 } 6063 6064 KeyHelp *WindowDelegateGetKeyHelp() override { 6065 static curses::KeyHelp g_source_view_key_help[] = { 6066 {'\t', "Select next view"}, 6067 {KEY_BTAB, "Select previous view"}, 6068 {'h', "Show help dialog with view specific key bindings"}, 6069 {',', "Page up"}, 6070 {'.', "Page down"}, 6071 {KEY_UP, "Select previous"}, 6072 {KEY_DOWN, "Select next"}, 6073 {KEY_LEFT, "Unexpand or select parent"}, 6074 {KEY_RIGHT, "Expand"}, 6075 {KEY_PPAGE, "Page up"}, 6076 {KEY_NPAGE, "Page down"}, 6077 {'\0', nullptr}}; 6078 return g_source_view_key_help; 6079 } 6080 6081 MenuActionResult MenuDelegateAction(Menu &menu) override { 6082 switch (menu.GetIdentifier()) { 6083 case eMenuID_TargetCreate: { 6084 WindowSP main_window_sp = m_app.GetMainWindow(); 6085 FormDelegateSP form_delegate_sp = 6086 FormDelegateSP(new TargetCreateFormDelegate(m_debugger)); 6087 Rect bounds = main_window_sp->GetCenteredRect(80, 19); 6088 WindowSP form_window_sp = main_window_sp->CreateSubWindow( 6089 form_delegate_sp->GetName().c_str(), bounds, true); 6090 WindowDelegateSP window_delegate_sp = 6091 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); 6092 form_window_sp->SetDelegate(window_delegate_sp); 6093 return MenuActionResult::Handled; 6094 } 6095 case eMenuID_ThreadStepIn: { 6096 ExecutionContext exe_ctx = 6097 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6098 if (exe_ctx.HasThreadScope()) { 6099 Process *process = exe_ctx.GetProcessPtr(); 6100 if (process && process->IsAlive() && 6101 StateIsStoppedState(process->GetState(), true)) 6102 exe_ctx.GetThreadRef().StepIn(true); 6103 } 6104 } 6105 return MenuActionResult::Handled; 6106 6107 case eMenuID_ThreadStepOut: { 6108 ExecutionContext exe_ctx = 6109 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6110 if (exe_ctx.HasThreadScope()) { 6111 Process *process = exe_ctx.GetProcessPtr(); 6112 if (process && process->IsAlive() && 6113 StateIsStoppedState(process->GetState(), true)) 6114 exe_ctx.GetThreadRef().StepOut(); 6115 } 6116 } 6117 return MenuActionResult::Handled; 6118 6119 case eMenuID_ThreadStepOver: { 6120 ExecutionContext exe_ctx = 6121 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6122 if (exe_ctx.HasThreadScope()) { 6123 Process *process = exe_ctx.GetProcessPtr(); 6124 if (process && process->IsAlive() && 6125 StateIsStoppedState(process->GetState(), true)) 6126 exe_ctx.GetThreadRef().StepOver(true); 6127 } 6128 } 6129 return MenuActionResult::Handled; 6130 6131 case eMenuID_ProcessAttach: { 6132 WindowSP main_window_sp = m_app.GetMainWindow(); 6133 FormDelegateSP form_delegate_sp = FormDelegateSP( 6134 new ProcessAttachFormDelegate(m_debugger, main_window_sp)); 6135 Rect bounds = main_window_sp->GetCenteredRect(80, 22); 6136 WindowSP form_window_sp = main_window_sp->CreateSubWindow( 6137 form_delegate_sp->GetName().c_str(), bounds, true); 6138 WindowDelegateSP window_delegate_sp = 6139 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); 6140 form_window_sp->SetDelegate(window_delegate_sp); 6141 return MenuActionResult::Handled; 6142 } 6143 case eMenuID_ProcessLaunch: { 6144 WindowSP main_window_sp = m_app.GetMainWindow(); 6145 FormDelegateSP form_delegate_sp = FormDelegateSP( 6146 new ProcessLaunchFormDelegate(m_debugger, main_window_sp)); 6147 Rect bounds = main_window_sp->GetCenteredRect(80, 22); 6148 WindowSP form_window_sp = main_window_sp->CreateSubWindow( 6149 form_delegate_sp->GetName().c_str(), bounds, true); 6150 WindowDelegateSP window_delegate_sp = 6151 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); 6152 form_window_sp->SetDelegate(window_delegate_sp); 6153 return MenuActionResult::Handled; 6154 } 6155 6156 case eMenuID_ProcessContinue: { 6157 ExecutionContext exe_ctx = 6158 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6159 if (exe_ctx.HasProcessScope()) { 6160 Process *process = exe_ctx.GetProcessPtr(); 6161 if (process && process->IsAlive() && 6162 StateIsStoppedState(process->GetState(), true)) 6163 process->Resume(); 6164 } 6165 } 6166 return MenuActionResult::Handled; 6167 6168 case eMenuID_ProcessKill: { 6169 ExecutionContext exe_ctx = 6170 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6171 if (exe_ctx.HasProcessScope()) { 6172 Process *process = exe_ctx.GetProcessPtr(); 6173 if (process && process->IsAlive()) 6174 process->Destroy(false); 6175 } 6176 } 6177 return MenuActionResult::Handled; 6178 6179 case eMenuID_ProcessHalt: { 6180 ExecutionContext exe_ctx = 6181 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6182 if (exe_ctx.HasProcessScope()) { 6183 Process *process = exe_ctx.GetProcessPtr(); 6184 if (process && process->IsAlive()) 6185 process->Halt(); 6186 } 6187 } 6188 return MenuActionResult::Handled; 6189 6190 case eMenuID_ProcessDetachResume: 6191 case eMenuID_ProcessDetachSuspended: { 6192 ExecutionContext exe_ctx = 6193 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6194 if (exe_ctx.HasProcessScope()) { 6195 Process *process = exe_ctx.GetProcessPtr(); 6196 if (process && process->IsAlive()) 6197 process->Detach(menu.GetIdentifier() == 6198 eMenuID_ProcessDetachSuspended); 6199 } 6200 } 6201 return MenuActionResult::Handled; 6202 6203 case eMenuID_Process: { 6204 // Populate the menu with all of the threads if the process is stopped 6205 // when the Process menu gets selected and is about to display its 6206 // submenu. 6207 Menus &submenus = menu.GetSubmenus(); 6208 ExecutionContext exe_ctx = 6209 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6210 Process *process = exe_ctx.GetProcessPtr(); 6211 if (process && process->IsAlive() && 6212 StateIsStoppedState(process->GetState(), true)) { 6213 if (submenus.size() == 7) 6214 menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 6215 else if (submenus.size() > 8) 6216 submenus.erase(submenus.begin() + 8, submenus.end()); 6217 6218 ThreadList &threads = process->GetThreadList(); 6219 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 6220 size_t num_threads = threads.GetSize(); 6221 for (size_t i = 0; i < num_threads; ++i) { 6222 ThreadSP thread_sp = threads.GetThreadAtIndex(i); 6223 char menu_char = '\0'; 6224 if (i < 9) 6225 menu_char = '1' + i; 6226 StreamString thread_menu_title; 6227 thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID()); 6228 const char *thread_name = thread_sp->GetName(); 6229 if (thread_name && thread_name[0]) 6230 thread_menu_title.Printf(" %s", thread_name); 6231 else { 6232 const char *queue_name = thread_sp->GetQueueName(); 6233 if (queue_name && queue_name[0]) 6234 thread_menu_title.Printf(" %s", queue_name); 6235 } 6236 menu.AddSubmenu( 6237 MenuSP(new Menu(thread_menu_title.GetString().str().c_str(), 6238 nullptr, menu_char, thread_sp->GetID()))); 6239 } 6240 } else if (submenus.size() > 7) { 6241 // Remove the separator and any other thread submenu items that were 6242 // previously added 6243 submenus.erase(submenus.begin() + 7, submenus.end()); 6244 } 6245 // Since we are adding and removing items we need to recalculate the 6246 // name lengths 6247 menu.RecalculateNameLengths(); 6248 } 6249 return MenuActionResult::Handled; 6250 6251 case eMenuID_ViewVariables: { 6252 WindowSP main_window_sp = m_app.GetMainWindow(); 6253 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 6254 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 6255 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 6256 const Rect source_bounds = source_window_sp->GetBounds(); 6257 6258 if (variables_window_sp) { 6259 const Rect variables_bounds = variables_window_sp->GetBounds(); 6260 6261 main_window_sp->RemoveSubWindow(variables_window_sp.get()); 6262 6263 if (registers_window_sp) { 6264 // We have a registers window, so give all the area back to the 6265 // registers window 6266 Rect registers_bounds = variables_bounds; 6267 registers_bounds.size.width = source_bounds.size.width; 6268 registers_window_sp->SetBounds(registers_bounds); 6269 } else { 6270 // We have no registers window showing so give the bottom area back 6271 // to the source view 6272 source_window_sp->Resize(source_bounds.size.width, 6273 source_bounds.size.height + 6274 variables_bounds.size.height); 6275 } 6276 } else { 6277 Rect new_variables_rect; 6278 if (registers_window_sp) { 6279 // We have a registers window so split the area of the registers 6280 // window into two columns where the left hand side will be the 6281 // variables and the right hand side will be the registers 6282 const Rect variables_bounds = registers_window_sp->GetBounds(); 6283 Rect new_registers_rect; 6284 variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect, 6285 new_registers_rect); 6286 registers_window_sp->SetBounds(new_registers_rect); 6287 } else { 6288 // No registers window, grab the bottom part of the source window 6289 Rect new_source_rect; 6290 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, 6291 new_variables_rect); 6292 source_window_sp->SetBounds(new_source_rect); 6293 } 6294 WindowSP new_window_sp = main_window_sp->CreateSubWindow( 6295 "Variables", new_variables_rect, false); 6296 new_window_sp->SetDelegate( 6297 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 6298 } 6299 touchwin(stdscr); 6300 } 6301 return MenuActionResult::Handled; 6302 6303 case eMenuID_ViewRegisters: { 6304 WindowSP main_window_sp = m_app.GetMainWindow(); 6305 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 6306 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 6307 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 6308 const Rect source_bounds = source_window_sp->GetBounds(); 6309 6310 if (registers_window_sp) { 6311 if (variables_window_sp) { 6312 const Rect variables_bounds = variables_window_sp->GetBounds(); 6313 6314 // We have a variables window, so give all the area back to the 6315 // variables window 6316 variables_window_sp->Resize(variables_bounds.size.width + 6317 registers_window_sp->GetWidth(), 6318 variables_bounds.size.height); 6319 } else { 6320 // We have no variables window showing so give the bottom area back 6321 // to the source view 6322 source_window_sp->Resize(source_bounds.size.width, 6323 source_bounds.size.height + 6324 registers_window_sp->GetHeight()); 6325 } 6326 main_window_sp->RemoveSubWindow(registers_window_sp.get()); 6327 } else { 6328 Rect new_regs_rect; 6329 if (variables_window_sp) { 6330 // We have a variables window, split it into two columns where the 6331 // left hand side will be the variables and the right hand side will 6332 // be the registers 6333 const Rect variables_bounds = variables_window_sp->GetBounds(); 6334 Rect new_vars_rect; 6335 variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect, 6336 new_regs_rect); 6337 variables_window_sp->SetBounds(new_vars_rect); 6338 } else { 6339 // No variables window, grab the bottom part of the source window 6340 Rect new_source_rect; 6341 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, 6342 new_regs_rect); 6343 source_window_sp->SetBounds(new_source_rect); 6344 } 6345 WindowSP new_window_sp = 6346 main_window_sp->CreateSubWindow("Registers", new_regs_rect, false); 6347 new_window_sp->SetDelegate( 6348 WindowDelegateSP(new RegistersWindowDelegate(m_debugger))); 6349 } 6350 touchwin(stdscr); 6351 } 6352 return MenuActionResult::Handled; 6353 6354 case eMenuID_ViewBreakpoints: { 6355 WindowSP main_window_sp = m_app.GetMainWindow(); 6356 WindowSP threads_window_sp = main_window_sp->FindSubWindow("Threads"); 6357 WindowSP breakpoints_window_sp = 6358 main_window_sp->FindSubWindow("Breakpoints"); 6359 const Rect threads_bounds = threads_window_sp->GetBounds(); 6360 6361 // If a breakpoints window already exists, remove it and give the area 6362 // it used to occupy to the threads window. If it doesn't exist, split 6363 // the threads window horizontally into two windows where the top window 6364 // is the threads window and the bottom window is a newly added 6365 // breakpoints window. 6366 if (breakpoints_window_sp) { 6367 threads_window_sp->Resize(threads_bounds.size.width, 6368 threads_bounds.size.height + 6369 breakpoints_window_sp->GetHeight()); 6370 main_window_sp->RemoveSubWindow(breakpoints_window_sp.get()); 6371 } else { 6372 Rect new_threads_bounds, breakpoints_bounds; 6373 threads_bounds.HorizontalSplitPercentage(0.70, new_threads_bounds, 6374 breakpoints_bounds); 6375 threads_window_sp->SetBounds(new_threads_bounds); 6376 breakpoints_window_sp = main_window_sp->CreateSubWindow( 6377 "Breakpoints", breakpoints_bounds, false); 6378 TreeDelegateSP breakpoints_delegate_sp( 6379 new BreakpointsTreeDelegate(m_debugger)); 6380 breakpoints_window_sp->SetDelegate(WindowDelegateSP( 6381 new TreeWindowDelegate(m_debugger, breakpoints_delegate_sp))); 6382 } 6383 touchwin(stdscr); 6384 return MenuActionResult::Handled; 6385 } 6386 6387 case eMenuID_HelpGUIHelp: 6388 m_app.GetMainWindow()->CreateHelpSubwindow(); 6389 return MenuActionResult::Handled; 6390 6391 default: 6392 break; 6393 } 6394 6395 return MenuActionResult::NotHandled; 6396 } 6397 6398 protected: 6399 Application &m_app; 6400 Debugger &m_debugger; 6401 }; 6402 6403 class StatusBarWindowDelegate : public WindowDelegate { 6404 public: 6405 StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) { 6406 FormatEntity::Parse("Thread: ${thread.id%tid}", m_format); 6407 } 6408 6409 ~StatusBarWindowDelegate() override = default; 6410 6411 bool WindowDelegateDraw(Window &window, bool force) override { 6412 ExecutionContext exe_ctx = 6413 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6414 Process *process = exe_ctx.GetProcessPtr(); 6415 Thread *thread = exe_ctx.GetThreadPtr(); 6416 StackFrame *frame = exe_ctx.GetFramePtr(); 6417 window.Erase(); 6418 window.SetBackground(BlackOnWhite); 6419 window.MoveCursor(0, 0); 6420 if (process) { 6421 const StateType state = process->GetState(); 6422 window.Printf("Process: %5" PRIu64 " %10s", process->GetID(), 6423 StateAsCString(state)); 6424 6425 if (StateIsStoppedState(state, true)) { 6426 StreamString strm; 6427 if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, 6428 nullptr, nullptr, false, false)) { 6429 window.MoveCursor(40, 0); 6430 window.PutCStringTruncated(1, strm.GetString().str().c_str()); 6431 } 6432 6433 window.MoveCursor(60, 0); 6434 if (frame) 6435 window.Printf("Frame: %3u PC = 0x%16.16" PRIx64, 6436 frame->GetFrameIndex(), 6437 frame->GetFrameCodeAddress().GetOpcodeLoadAddress( 6438 exe_ctx.GetTargetPtr())); 6439 } else if (state == eStateExited) { 6440 const char *exit_desc = process->GetExitDescription(); 6441 const int exit_status = process->GetExitStatus(); 6442 if (exit_desc && exit_desc[0]) 6443 window.Printf(" with status = %i (%s)", exit_status, exit_desc); 6444 else 6445 window.Printf(" with status = %i", exit_status); 6446 } 6447 } 6448 return true; 6449 } 6450 6451 protected: 6452 Debugger &m_debugger; 6453 FormatEntity::Entry m_format; 6454 }; 6455 6456 class SourceFileWindowDelegate : public WindowDelegate { 6457 public: 6458 SourceFileWindowDelegate(Debugger &debugger) 6459 : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(), 6460 m_disassembly_scope(nullptr), m_disassembly_sp(), m_disassembly_range(), 6461 m_title(), m_tid(LLDB_INVALID_THREAD_ID), m_line_width(4), 6462 m_selected_line(0), m_pc_line(0), m_stop_id(0), m_frame_idx(UINT32_MAX), 6463 m_first_visible_line(0), m_first_visible_column(0), m_min_x(0), 6464 m_min_y(0), m_max_x(0), m_max_y(0) {} 6465 6466 ~SourceFileWindowDelegate() override = default; 6467 6468 void Update(const SymbolContext &sc) { m_sc = sc; } 6469 6470 uint32_t NumVisibleLines() const { return m_max_y - m_min_y; } 6471 6472 const char *WindowDelegateGetHelpText() override { 6473 return "Source/Disassembly window keyboard shortcuts:"; 6474 } 6475 6476 KeyHelp *WindowDelegateGetKeyHelp() override { 6477 static curses::KeyHelp g_source_view_key_help[] = { 6478 {KEY_RETURN, "Run to selected line with one shot breakpoint"}, 6479 {KEY_UP, "Select previous source line"}, 6480 {KEY_DOWN, "Select next source line"}, 6481 {KEY_LEFT, "Scroll to the left"}, 6482 {KEY_RIGHT, "Scroll to the right"}, 6483 {KEY_PPAGE, "Page up"}, 6484 {KEY_NPAGE, "Page down"}, 6485 {'b', "Set breakpoint on selected source/disassembly line"}, 6486 {'c', "Continue process"}, 6487 {'D', "Detach with process suspended"}, 6488 {'h', "Show help dialog"}, 6489 {'n', "Step over (source line)"}, 6490 {'N', "Step over (single instruction)"}, 6491 {'f', "Step out (finish)"}, 6492 {'s', "Step in (source line)"}, 6493 {'S', "Step in (single instruction)"}, 6494 {'u', "Frame up"}, 6495 {'d', "Frame down"}, 6496 {',', "Page up"}, 6497 {'.', "Page down"}, 6498 {'\0', nullptr}}; 6499 return g_source_view_key_help; 6500 } 6501 6502 bool WindowDelegateDraw(Window &window, bool force) override { 6503 ExecutionContext exe_ctx = 6504 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6505 Process *process = exe_ctx.GetProcessPtr(); 6506 Thread *thread = nullptr; 6507 6508 bool update_location = false; 6509 if (process) { 6510 StateType state = process->GetState(); 6511 if (StateIsStoppedState(state, true)) { 6512 // We are stopped, so it is ok to 6513 update_location = true; 6514 } 6515 } 6516 6517 m_min_x = 1; 6518 m_min_y = 2; 6519 m_max_x = window.GetMaxX() - 1; 6520 m_max_y = window.GetMaxY() - 1; 6521 6522 const uint32_t num_visible_lines = NumVisibleLines(); 6523 StackFrameSP frame_sp; 6524 bool set_selected_line_to_pc = false; 6525 6526 if (update_location) { 6527 const bool process_alive = process ? process->IsAlive() : false; 6528 bool thread_changed = false; 6529 if (process_alive) { 6530 thread = exe_ctx.GetThreadPtr(); 6531 if (thread) { 6532 frame_sp = thread->GetSelectedFrame(); 6533 auto tid = thread->GetID(); 6534 thread_changed = tid != m_tid; 6535 m_tid = tid; 6536 } else { 6537 if (m_tid != LLDB_INVALID_THREAD_ID) { 6538 thread_changed = true; 6539 m_tid = LLDB_INVALID_THREAD_ID; 6540 } 6541 } 6542 } 6543 const uint32_t stop_id = process ? process->GetStopID() : 0; 6544 const bool stop_id_changed = stop_id != m_stop_id; 6545 bool frame_changed = false; 6546 m_stop_id = stop_id; 6547 m_title.Clear(); 6548 if (frame_sp) { 6549 m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything); 6550 if (m_sc.module_sp) { 6551 m_title.Printf( 6552 "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString()); 6553 ConstString func_name = m_sc.GetFunctionName(); 6554 if (func_name) 6555 m_title.Printf("`%s", func_name.GetCString()); 6556 } 6557 const uint32_t frame_idx = frame_sp->GetFrameIndex(); 6558 frame_changed = frame_idx != m_frame_idx; 6559 m_frame_idx = frame_idx; 6560 } else { 6561 m_sc.Clear(true); 6562 frame_changed = m_frame_idx != UINT32_MAX; 6563 m_frame_idx = UINT32_MAX; 6564 } 6565 6566 const bool context_changed = 6567 thread_changed || frame_changed || stop_id_changed; 6568 6569 if (process_alive) { 6570 if (m_sc.line_entry.IsValid()) { 6571 m_pc_line = m_sc.line_entry.line; 6572 if (m_pc_line != UINT32_MAX) 6573 --m_pc_line; // Convert to zero based line number... 6574 // Update the selected line if the stop ID changed... 6575 if (context_changed) 6576 m_selected_line = m_pc_line; 6577 6578 if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) { 6579 // Same file, nothing to do, we should either have the lines or 6580 // not (source file missing) 6581 if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) { 6582 if (m_selected_line >= m_first_visible_line + num_visible_lines) 6583 m_first_visible_line = m_selected_line - 10; 6584 } else { 6585 if (m_selected_line > 10) 6586 m_first_visible_line = m_selected_line - 10; 6587 else 6588 m_first_visible_line = 0; 6589 } 6590 } else { 6591 // File changed, set selected line to the line with the PC 6592 m_selected_line = m_pc_line; 6593 m_file_sp = 6594 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file); 6595 if (m_file_sp) { 6596 const size_t num_lines = m_file_sp->GetNumLines(); 6597 m_line_width = 1; 6598 for (size_t n = num_lines; n >= 10; n = n / 10) 6599 ++m_line_width; 6600 6601 if (num_lines < num_visible_lines || 6602 m_selected_line < num_visible_lines) 6603 m_first_visible_line = 0; 6604 else 6605 m_first_visible_line = m_selected_line - 10; 6606 } 6607 } 6608 } else { 6609 m_file_sp.reset(); 6610 } 6611 6612 if (!m_file_sp || m_file_sp->GetNumLines() == 0) { 6613 // Show disassembly 6614 bool prefer_file_cache = false; 6615 if (m_sc.function) { 6616 if (m_disassembly_scope != m_sc.function) { 6617 m_disassembly_scope = m_sc.function; 6618 m_disassembly_sp = m_sc.function->GetInstructions( 6619 exe_ctx, nullptr, !prefer_file_cache); 6620 if (m_disassembly_sp) { 6621 set_selected_line_to_pc = true; 6622 m_disassembly_range = m_sc.function->GetAddressRange(); 6623 } else { 6624 m_disassembly_range.Clear(); 6625 } 6626 } else { 6627 set_selected_line_to_pc = context_changed; 6628 } 6629 } else if (m_sc.symbol) { 6630 if (m_disassembly_scope != m_sc.symbol) { 6631 m_disassembly_scope = m_sc.symbol; 6632 m_disassembly_sp = m_sc.symbol->GetInstructions( 6633 exe_ctx, nullptr, prefer_file_cache); 6634 if (m_disassembly_sp) { 6635 set_selected_line_to_pc = true; 6636 m_disassembly_range.GetBaseAddress() = 6637 m_sc.symbol->GetAddress(); 6638 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize()); 6639 } else { 6640 m_disassembly_range.Clear(); 6641 } 6642 } else { 6643 set_selected_line_to_pc = context_changed; 6644 } 6645 } 6646 } 6647 } else { 6648 m_pc_line = UINT32_MAX; 6649 } 6650 } 6651 6652 const int window_width = window.GetWidth(); 6653 window.Erase(); 6654 window.DrawTitleBox("Sources"); 6655 if (!m_title.GetString().empty()) { 6656 window.AttributeOn(A_REVERSE); 6657 window.MoveCursor(1, 1); 6658 window.PutChar(' '); 6659 window.PutCStringTruncated(1, m_title.GetString().str().c_str()); 6660 int x = window.GetCursorX(); 6661 if (x < window_width - 1) { 6662 window.Printf("%*s", window_width - x - 1, ""); 6663 } 6664 window.AttributeOff(A_REVERSE); 6665 } 6666 6667 Target *target = exe_ctx.GetTargetPtr(); 6668 const size_t num_source_lines = GetNumSourceLines(); 6669 if (num_source_lines > 0) { 6670 // Display source 6671 BreakpointLines bp_lines; 6672 if (target) { 6673 BreakpointList &bp_list = target->GetBreakpointList(); 6674 const size_t num_bps = bp_list.GetSize(); 6675 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 6676 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 6677 const size_t num_bps_locs = bp_sp->GetNumLocations(); 6678 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 6679 BreakpointLocationSP bp_loc_sp = 6680 bp_sp->GetLocationAtIndex(bp_loc_idx); 6681 LineEntry bp_loc_line_entry; 6682 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( 6683 bp_loc_line_entry)) { 6684 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) { 6685 bp_lines.insert(bp_loc_line_entry.line); 6686 } 6687 } 6688 } 6689 } 6690 } 6691 6692 const attr_t selected_highlight_attr = A_REVERSE; 6693 const attr_t pc_highlight_attr = COLOR_PAIR(BlackOnBlue); 6694 6695 for (size_t i = 0; i < num_visible_lines; ++i) { 6696 const uint32_t curr_line = m_first_visible_line + i; 6697 if (curr_line < num_source_lines) { 6698 const int line_y = m_min_y + i; 6699 window.MoveCursor(1, line_y); 6700 const bool is_pc_line = curr_line == m_pc_line; 6701 const bool line_is_selected = m_selected_line == curr_line; 6702 // Highlight the line as the PC line first, then if the selected 6703 // line isn't the same as the PC line, highlight it differently 6704 attr_t highlight_attr = 0; 6705 attr_t bp_attr = 0; 6706 if (is_pc_line) 6707 highlight_attr = pc_highlight_attr; 6708 else if (line_is_selected) 6709 highlight_attr = selected_highlight_attr; 6710 6711 if (bp_lines.find(curr_line + 1) != bp_lines.end()) 6712 bp_attr = COLOR_PAIR(BlackOnWhite); 6713 6714 if (bp_attr) 6715 window.AttributeOn(bp_attr); 6716 6717 window.Printf(" %*u ", m_line_width, curr_line + 1); 6718 6719 if (bp_attr) 6720 window.AttributeOff(bp_attr); 6721 6722 window.PutChar(ACS_VLINE); 6723 // Mark the line with the PC with a diamond 6724 if (is_pc_line) 6725 window.PutChar(ACS_DIAMOND); 6726 else 6727 window.PutChar(' '); 6728 6729 if (highlight_attr) 6730 window.AttributeOn(highlight_attr); 6731 6732 StreamString lineStream; 6733 m_file_sp->DisplaySourceLines(curr_line + 1, {}, 0, 0, &lineStream); 6734 StringRef line = lineStream.GetString(); 6735 if (line.endswith("\n")) 6736 line = line.drop_back(); 6737 bool wasWritten = window.OutputColoredStringTruncated( 6738 1, line, m_first_visible_column, line_is_selected); 6739 if (line_is_selected && !wasWritten) { 6740 // Draw an empty space to show the selected line if empty, 6741 // or draw '<' if nothing is visible because of scrolling too much 6742 // to the right. 6743 window.PutCStringTruncated( 6744 1, line.empty() && m_first_visible_column == 0 ? " " : "<"); 6745 } 6746 6747 if (is_pc_line && frame_sp && 6748 frame_sp->GetConcreteFrameIndex() == 0) { 6749 StopInfoSP stop_info_sp; 6750 if (thread) 6751 stop_info_sp = thread->GetStopInfo(); 6752 if (stop_info_sp) { 6753 const char *stop_description = stop_info_sp->GetDescription(); 6754 if (stop_description && stop_description[0]) { 6755 size_t stop_description_len = strlen(stop_description); 6756 int desc_x = window_width - stop_description_len - 16; 6757 if (desc_x - window.GetCursorX() > 0) 6758 window.Printf("%*s", desc_x - window.GetCursorX(), ""); 6759 window.MoveCursor(window_width - stop_description_len - 16, 6760 line_y); 6761 const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue); 6762 window.AttributeOn(stop_reason_attr); 6763 window.PrintfTruncated(1, " <<< Thread %u: %s ", 6764 thread->GetIndexID(), stop_description); 6765 window.AttributeOff(stop_reason_attr); 6766 } 6767 } else { 6768 window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); 6769 } 6770 } 6771 if (highlight_attr) 6772 window.AttributeOff(highlight_attr); 6773 } else { 6774 break; 6775 } 6776 } 6777 } else { 6778 size_t num_disassembly_lines = GetNumDisassemblyLines(); 6779 if (num_disassembly_lines > 0) { 6780 // Display disassembly 6781 BreakpointAddrs bp_file_addrs; 6782 Target *target = exe_ctx.GetTargetPtr(); 6783 if (target) { 6784 BreakpointList &bp_list = target->GetBreakpointList(); 6785 const size_t num_bps = bp_list.GetSize(); 6786 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 6787 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 6788 const size_t num_bps_locs = bp_sp->GetNumLocations(); 6789 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; 6790 ++bp_loc_idx) { 6791 BreakpointLocationSP bp_loc_sp = 6792 bp_sp->GetLocationAtIndex(bp_loc_idx); 6793 LineEntry bp_loc_line_entry; 6794 const lldb::addr_t file_addr = 6795 bp_loc_sp->GetAddress().GetFileAddress(); 6796 if (file_addr != LLDB_INVALID_ADDRESS) { 6797 if (m_disassembly_range.ContainsFileAddress(file_addr)) 6798 bp_file_addrs.insert(file_addr); 6799 } 6800 } 6801 } 6802 } 6803 6804 const attr_t selected_highlight_attr = A_REVERSE; 6805 const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue); 6806 6807 StreamString strm; 6808 6809 InstructionList &insts = m_disassembly_sp->GetInstructionList(); 6810 Address pc_address; 6811 6812 if (frame_sp) 6813 pc_address = frame_sp->GetFrameCodeAddress(); 6814 const uint32_t pc_idx = 6815 pc_address.IsValid() 6816 ? insts.GetIndexOfInstructionAtAddress(pc_address) 6817 : UINT32_MAX; 6818 if (set_selected_line_to_pc) { 6819 m_selected_line = pc_idx; 6820 } 6821 6822 const uint32_t non_visible_pc_offset = (num_visible_lines / 5); 6823 if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines) 6824 m_first_visible_line = 0; 6825 6826 if (pc_idx < num_disassembly_lines) { 6827 if (pc_idx < static_cast<uint32_t>(m_first_visible_line) || 6828 pc_idx >= m_first_visible_line + num_visible_lines) 6829 m_first_visible_line = pc_idx - non_visible_pc_offset; 6830 } 6831 6832 for (size_t i = 0; i < num_visible_lines; ++i) { 6833 const uint32_t inst_idx = m_first_visible_line + i; 6834 Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get(); 6835 if (!inst) 6836 break; 6837 6838 const int line_y = m_min_y + i; 6839 window.MoveCursor(1, line_y); 6840 const bool is_pc_line = frame_sp && inst_idx == pc_idx; 6841 const bool line_is_selected = m_selected_line == inst_idx; 6842 // Highlight the line as the PC line first, then if the selected 6843 // line isn't the same as the PC line, highlight it differently 6844 attr_t highlight_attr = 0; 6845 attr_t bp_attr = 0; 6846 if (is_pc_line) 6847 highlight_attr = pc_highlight_attr; 6848 else if (line_is_selected) 6849 highlight_attr = selected_highlight_attr; 6850 6851 if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) != 6852 bp_file_addrs.end()) 6853 bp_attr = COLOR_PAIR(BlackOnWhite); 6854 6855 if (bp_attr) 6856 window.AttributeOn(bp_attr); 6857 6858 window.Printf(" 0x%16.16llx ", 6859 static_cast<unsigned long long>( 6860 inst->GetAddress().GetLoadAddress(target))); 6861 6862 if (bp_attr) 6863 window.AttributeOff(bp_attr); 6864 6865 window.PutChar(ACS_VLINE); 6866 // Mark the line with the PC with a diamond 6867 if (is_pc_line) 6868 window.PutChar(ACS_DIAMOND); 6869 else 6870 window.PutChar(' '); 6871 6872 if (highlight_attr) 6873 window.AttributeOn(highlight_attr); 6874 6875 const char *mnemonic = inst->GetMnemonic(&exe_ctx); 6876 const char *operands = inst->GetOperands(&exe_ctx); 6877 const char *comment = inst->GetComment(&exe_ctx); 6878 6879 if (mnemonic != nullptr && mnemonic[0] == '\0') 6880 mnemonic = nullptr; 6881 if (operands != nullptr && operands[0] == '\0') 6882 operands = nullptr; 6883 if (comment != nullptr && comment[0] == '\0') 6884 comment = nullptr; 6885 6886 strm.Clear(); 6887 6888 if (mnemonic != nullptr && operands != nullptr && comment != nullptr) 6889 strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment); 6890 else if (mnemonic != nullptr && operands != nullptr) 6891 strm.Printf("%-8s %s", mnemonic, operands); 6892 else if (mnemonic != nullptr) 6893 strm.Printf("%s", mnemonic); 6894 6895 int right_pad = 1; 6896 window.PutCStringTruncated( 6897 right_pad, 6898 strm.GetString().substr(m_first_visible_column).data()); 6899 6900 if (is_pc_line && frame_sp && 6901 frame_sp->GetConcreteFrameIndex() == 0) { 6902 StopInfoSP stop_info_sp; 6903 if (thread) 6904 stop_info_sp = thread->GetStopInfo(); 6905 if (stop_info_sp) { 6906 const char *stop_description = stop_info_sp->GetDescription(); 6907 if (stop_description && stop_description[0]) { 6908 size_t stop_description_len = strlen(stop_description); 6909 int desc_x = window_width - stop_description_len - 16; 6910 if (desc_x - window.GetCursorX() > 0) 6911 window.Printf("%*s", desc_x - window.GetCursorX(), ""); 6912 window.MoveCursor(window_width - stop_description_len - 15, 6913 line_y); 6914 window.PrintfTruncated(1, "<<< Thread %u: %s ", 6915 thread->GetIndexID(), stop_description); 6916 } 6917 } else { 6918 window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); 6919 } 6920 } 6921 if (highlight_attr) 6922 window.AttributeOff(highlight_attr); 6923 } 6924 } 6925 } 6926 return true; // Drawing handled 6927 } 6928 6929 size_t GetNumLines() { 6930 size_t num_lines = GetNumSourceLines(); 6931 if (num_lines == 0) 6932 num_lines = GetNumDisassemblyLines(); 6933 return num_lines; 6934 } 6935 6936 size_t GetNumSourceLines() const { 6937 if (m_file_sp) 6938 return m_file_sp->GetNumLines(); 6939 return 0; 6940 } 6941 6942 size_t GetNumDisassemblyLines() const { 6943 if (m_disassembly_sp) 6944 return m_disassembly_sp->GetInstructionList().GetSize(); 6945 return 0; 6946 } 6947 6948 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 6949 const uint32_t num_visible_lines = NumVisibleLines(); 6950 const size_t num_lines = GetNumLines(); 6951 6952 switch (c) { 6953 case ',': 6954 case KEY_PPAGE: 6955 // Page up key 6956 if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines) 6957 m_first_visible_line -= num_visible_lines; 6958 else 6959 m_first_visible_line = 0; 6960 m_selected_line = m_first_visible_line; 6961 return eKeyHandled; 6962 6963 case '.': 6964 case KEY_NPAGE: 6965 // Page down key 6966 { 6967 if (m_first_visible_line + num_visible_lines < num_lines) 6968 m_first_visible_line += num_visible_lines; 6969 else if (num_lines < num_visible_lines) 6970 m_first_visible_line = 0; 6971 else 6972 m_first_visible_line = num_lines - num_visible_lines; 6973 m_selected_line = m_first_visible_line; 6974 } 6975 return eKeyHandled; 6976 6977 case KEY_UP: 6978 if (m_selected_line > 0) { 6979 m_selected_line--; 6980 if (static_cast<size_t>(m_first_visible_line) > m_selected_line) 6981 m_first_visible_line = m_selected_line; 6982 } 6983 return eKeyHandled; 6984 6985 case KEY_DOWN: 6986 if (m_selected_line + 1 < num_lines) { 6987 m_selected_line++; 6988 if (m_first_visible_line + num_visible_lines < m_selected_line) 6989 m_first_visible_line++; 6990 } 6991 return eKeyHandled; 6992 6993 case KEY_LEFT: 6994 if (m_first_visible_column > 0) 6995 --m_first_visible_column; 6996 return eKeyHandled; 6997 6998 case KEY_RIGHT: 6999 ++m_first_visible_column; 7000 return eKeyHandled; 7001 7002 case '\r': 7003 case '\n': 7004 case KEY_ENTER: 7005 // Set a breakpoint and run to the line using a one shot breakpoint 7006 if (GetNumSourceLines() > 0) { 7007 ExecutionContext exe_ctx = 7008 m_debugger.GetCommandInterpreter().GetExecutionContext(); 7009 if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) { 7010 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 7011 nullptr, // Don't limit the breakpoint to certain modules 7012 m_file_sp->GetFileSpec(), // Source file 7013 m_selected_line + 7014 1, // Source line number (m_selected_line is zero based) 7015 0, // Unspecified column. 7016 0, // No offset 7017 eLazyBoolCalculate, // Check inlines using global setting 7018 eLazyBoolCalculate, // Skip prologue using global setting, 7019 false, // internal 7020 false, // request_hardware 7021 eLazyBoolCalculate); // move_to_nearest_code 7022 // Make breakpoint one shot 7023 bp_sp->GetOptions().SetOneShot(true); 7024 exe_ctx.GetProcessRef().Resume(); 7025 } 7026 } else if (m_selected_line < GetNumDisassemblyLines()) { 7027 const Instruction *inst = m_disassembly_sp->GetInstructionList() 7028 .GetInstructionAtIndex(m_selected_line) 7029 .get(); 7030 ExecutionContext exe_ctx = 7031 m_debugger.GetCommandInterpreter().GetExecutionContext(); 7032 if (exe_ctx.HasTargetScope()) { 7033 Address addr = inst->GetAddress(); 7034 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 7035 addr, // lldb_private::Address 7036 false, // internal 7037 false); // request_hardware 7038 // Make breakpoint one shot 7039 bp_sp->GetOptions().SetOneShot(true); 7040 exe_ctx.GetProcessRef().Resume(); 7041 } 7042 } 7043 return eKeyHandled; 7044 7045 case 'b': // 'b' == toggle breakpoint on currently selected line 7046 ToggleBreakpointOnSelectedLine(); 7047 return eKeyHandled; 7048 7049 case 'D': // 'D' == detach and keep stopped 7050 { 7051 ExecutionContext exe_ctx = 7052 m_debugger.GetCommandInterpreter().GetExecutionContext(); 7053 if (exe_ctx.HasProcessScope()) 7054 exe_ctx.GetProcessRef().Detach(true); 7055 } 7056 return eKeyHandled; 7057 7058 case 'c': 7059 // 'c' == continue 7060 { 7061 ExecutionContext exe_ctx = 7062 m_debugger.GetCommandInterpreter().GetExecutionContext(); 7063 if (exe_ctx.HasProcessScope()) 7064 exe_ctx.GetProcessRef().Resume(); 7065 } 7066 return eKeyHandled; 7067 7068 case 'f': 7069 // 'f' == step out (finish) 7070 { 7071 ExecutionContext exe_ctx = 7072 m_debugger.GetCommandInterpreter().GetExecutionContext(); 7073 if (exe_ctx.HasThreadScope() && 7074 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 7075 exe_ctx.GetThreadRef().StepOut(); 7076 } 7077 } 7078 return eKeyHandled; 7079 7080 case 'n': // 'n' == step over 7081 case 'N': // 'N' == step over instruction 7082 { 7083 ExecutionContext exe_ctx = 7084 m_debugger.GetCommandInterpreter().GetExecutionContext(); 7085 if (exe_ctx.HasThreadScope() && 7086 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 7087 bool source_step = (c == 'n'); 7088 exe_ctx.GetThreadRef().StepOver(source_step); 7089 } 7090 } 7091 return eKeyHandled; 7092 7093 case 's': // 's' == step into 7094 case 'S': // 'S' == step into instruction 7095 { 7096 ExecutionContext exe_ctx = 7097 m_debugger.GetCommandInterpreter().GetExecutionContext(); 7098 if (exe_ctx.HasThreadScope() && 7099 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 7100 bool source_step = (c == 's'); 7101 exe_ctx.GetThreadRef().StepIn(source_step); 7102 } 7103 } 7104 return eKeyHandled; 7105 7106 case 'u': // 'u' == frame up 7107 case 'd': // 'd' == frame down 7108 { 7109 ExecutionContext exe_ctx = 7110 m_debugger.GetCommandInterpreter().GetExecutionContext(); 7111 if (exe_ctx.HasThreadScope()) { 7112 Thread *thread = exe_ctx.GetThreadPtr(); 7113 uint32_t frame_idx = thread->GetSelectedFrameIndex(); 7114 if (frame_idx == UINT32_MAX) 7115 frame_idx = 0; 7116 if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount()) 7117 ++frame_idx; 7118 else if (c == 'd' && frame_idx > 0) 7119 --frame_idx; 7120 if (thread->SetSelectedFrameByIndex(frame_idx, true)) 7121 exe_ctx.SetFrameSP(thread->GetSelectedFrame()); 7122 } 7123 } 7124 return eKeyHandled; 7125 7126 case 'h': 7127 window.CreateHelpSubwindow(); 7128 return eKeyHandled; 7129 7130 default: 7131 break; 7132 } 7133 return eKeyNotHandled; 7134 } 7135 7136 void ToggleBreakpointOnSelectedLine() { 7137 ExecutionContext exe_ctx = 7138 m_debugger.GetCommandInterpreter().GetExecutionContext(); 7139 if (!exe_ctx.HasTargetScope()) 7140 return; 7141 if (GetNumSourceLines() > 0) { 7142 // Source file breakpoint. 7143 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); 7144 const size_t num_bps = bp_list.GetSize(); 7145 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 7146 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 7147 const size_t num_bps_locs = bp_sp->GetNumLocations(); 7148 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 7149 BreakpointLocationSP bp_loc_sp = 7150 bp_sp->GetLocationAtIndex(bp_loc_idx); 7151 LineEntry bp_loc_line_entry; 7152 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( 7153 bp_loc_line_entry)) { 7154 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file && 7155 m_selected_line + 1 == bp_loc_line_entry.line) { 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 } 7165 // No breakpoint found on the location, add it. 7166 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 7167 nullptr, // Don't limit the breakpoint to certain modules 7168 m_file_sp->GetFileSpec(), // Source file 7169 m_selected_line + 7170 1, // Source line number (m_selected_line is zero based) 7171 0, // No column specified. 7172 0, // No offset 7173 eLazyBoolCalculate, // Check inlines using global setting 7174 eLazyBoolCalculate, // Skip prologue using global setting, 7175 false, // internal 7176 false, // request_hardware 7177 eLazyBoolCalculate); // move_to_nearest_code 7178 } else { 7179 // Disassembly breakpoint. 7180 assert(GetNumDisassemblyLines() > 0); 7181 assert(m_selected_line < GetNumDisassemblyLines()); 7182 const Instruction *inst = m_disassembly_sp->GetInstructionList() 7183 .GetInstructionAtIndex(m_selected_line) 7184 .get(); 7185 Address addr = inst->GetAddress(); 7186 // Try to find it. 7187 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); 7188 const size_t num_bps = bp_list.GetSize(); 7189 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 7190 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 7191 const size_t num_bps_locs = bp_sp->GetNumLocations(); 7192 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 7193 BreakpointLocationSP bp_loc_sp = 7194 bp_sp->GetLocationAtIndex(bp_loc_idx); 7195 LineEntry bp_loc_line_entry; 7196 const lldb::addr_t file_addr = 7197 bp_loc_sp->GetAddress().GetFileAddress(); 7198 if (file_addr == addr.GetFileAddress()) { 7199 bool removed = 7200 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); 7201 assert(removed); 7202 UNUSED_IF_ASSERT_DISABLED(removed); 7203 return; // Existing breakpoint removed. 7204 } 7205 } 7206 } 7207 // No breakpoint found on the address, add it. 7208 BreakpointSP bp_sp = 7209 exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address 7210 false, // internal 7211 false); // request_hardware 7212 } 7213 } 7214 7215 protected: 7216 typedef std::set<uint32_t> BreakpointLines; 7217 typedef std::set<lldb::addr_t> BreakpointAddrs; 7218 7219 Debugger &m_debugger; 7220 SymbolContext m_sc; 7221 SourceManager::FileSP m_file_sp; 7222 SymbolContextScope *m_disassembly_scope; 7223 lldb::DisassemblerSP m_disassembly_sp; 7224 AddressRange m_disassembly_range; 7225 StreamString m_title; 7226 lldb::user_id_t m_tid; 7227 int m_line_width; 7228 uint32_t m_selected_line; // The selected line 7229 uint32_t m_pc_line; // The line with the PC 7230 uint32_t m_stop_id; 7231 uint32_t m_frame_idx; 7232 int m_first_visible_line; 7233 int m_first_visible_column; 7234 int m_min_x; 7235 int m_min_y; 7236 int m_max_x; 7237 int m_max_y; 7238 }; 7239 7240 DisplayOptions ValueObjectListDelegate::g_options = {true}; 7241 7242 IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger) 7243 : IOHandler(debugger, IOHandler::Type::Curses) {} 7244 7245 void IOHandlerCursesGUI::Activate() { 7246 IOHandler::Activate(); 7247 if (!m_app_ap) { 7248 m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE()); 7249 7250 // This is both a window and a menu delegate 7251 std::shared_ptr<ApplicationDelegate> app_delegate_sp( 7252 new ApplicationDelegate(*m_app_ap, m_debugger)); 7253 7254 MenuDelegateSP app_menu_delegate_sp = 7255 std::static_pointer_cast<MenuDelegate>(app_delegate_sp); 7256 MenuSP lldb_menu_sp( 7257 new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB)); 7258 MenuSP exit_menuitem_sp( 7259 new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit)); 7260 exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit); 7261 lldb_menu_sp->AddSubmenu(MenuSP(new Menu( 7262 "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout))); 7263 lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 7264 lldb_menu_sp->AddSubmenu(exit_menuitem_sp); 7265 7266 MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2), 7267 ApplicationDelegate::eMenuID_Target)); 7268 target_menu_sp->AddSubmenu(MenuSP(new Menu( 7269 "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate))); 7270 target_menu_sp->AddSubmenu(MenuSP(new Menu( 7271 "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete))); 7272 7273 MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3), 7274 ApplicationDelegate::eMenuID_Process)); 7275 process_menu_sp->AddSubmenu(MenuSP(new Menu( 7276 "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach))); 7277 process_menu_sp->AddSubmenu( 7278 MenuSP(new Menu("Detach and resume", nullptr, 'd', 7279 ApplicationDelegate::eMenuID_ProcessDetachResume))); 7280 process_menu_sp->AddSubmenu( 7281 MenuSP(new Menu("Detach suspended", nullptr, 's', 7282 ApplicationDelegate::eMenuID_ProcessDetachSuspended))); 7283 process_menu_sp->AddSubmenu(MenuSP(new Menu( 7284 "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch))); 7285 process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 7286 process_menu_sp->AddSubmenu( 7287 MenuSP(new Menu("Continue", nullptr, 'c', 7288 ApplicationDelegate::eMenuID_ProcessContinue))); 7289 process_menu_sp->AddSubmenu(MenuSP(new Menu( 7290 "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt))); 7291 process_menu_sp->AddSubmenu(MenuSP(new Menu( 7292 "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill))); 7293 7294 MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4), 7295 ApplicationDelegate::eMenuID_Thread)); 7296 thread_menu_sp->AddSubmenu(MenuSP(new Menu( 7297 "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn))); 7298 thread_menu_sp->AddSubmenu( 7299 MenuSP(new Menu("Step Over", nullptr, 'v', 7300 ApplicationDelegate::eMenuID_ThreadStepOver))); 7301 thread_menu_sp->AddSubmenu(MenuSP(new Menu( 7302 "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut))); 7303 7304 MenuSP view_menu_sp( 7305 new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View)); 7306 view_menu_sp->AddSubmenu( 7307 MenuSP(new Menu("Backtrace", nullptr, 't', 7308 ApplicationDelegate::eMenuID_ViewBacktrace))); 7309 view_menu_sp->AddSubmenu( 7310 MenuSP(new Menu("Registers", nullptr, 'r', 7311 ApplicationDelegate::eMenuID_ViewRegisters))); 7312 view_menu_sp->AddSubmenu(MenuSP(new Menu( 7313 "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource))); 7314 view_menu_sp->AddSubmenu( 7315 MenuSP(new Menu("Variables", nullptr, 'v', 7316 ApplicationDelegate::eMenuID_ViewVariables))); 7317 view_menu_sp->AddSubmenu( 7318 MenuSP(new Menu("Breakpoints", nullptr, 'b', 7319 ApplicationDelegate::eMenuID_ViewBreakpoints))); 7320 7321 MenuSP help_menu_sp( 7322 new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help)); 7323 help_menu_sp->AddSubmenu(MenuSP(new Menu( 7324 "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp))); 7325 7326 m_app_ap->Initialize(); 7327 WindowSP &main_window_sp = m_app_ap->GetMainWindow(); 7328 7329 MenuSP menubar_sp(new Menu(Menu::Type::Bar)); 7330 menubar_sp->AddSubmenu(lldb_menu_sp); 7331 menubar_sp->AddSubmenu(target_menu_sp); 7332 menubar_sp->AddSubmenu(process_menu_sp); 7333 menubar_sp->AddSubmenu(thread_menu_sp); 7334 menubar_sp->AddSubmenu(view_menu_sp); 7335 menubar_sp->AddSubmenu(help_menu_sp); 7336 menubar_sp->SetDelegate(app_menu_delegate_sp); 7337 7338 Rect content_bounds = main_window_sp->GetFrame(); 7339 Rect menubar_bounds = content_bounds.MakeMenuBar(); 7340 Rect status_bounds = content_bounds.MakeStatusBar(); 7341 Rect source_bounds; 7342 Rect variables_bounds; 7343 Rect threads_bounds; 7344 Rect source_variables_bounds; 7345 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, 7346 threads_bounds); 7347 source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds, 7348 variables_bounds); 7349 7350 WindowSP menubar_window_sp = 7351 main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false); 7352 // Let the menubar get keys if the active window doesn't handle the keys 7353 // that are typed so it can respond to menubar key presses. 7354 menubar_window_sp->SetCanBeActive( 7355 false); // Don't let the menubar become the active window 7356 menubar_window_sp->SetDelegate(menubar_sp); 7357 7358 WindowSP source_window_sp( 7359 main_window_sp->CreateSubWindow("Source", source_bounds, true)); 7360 WindowSP variables_window_sp( 7361 main_window_sp->CreateSubWindow("Variables", variables_bounds, false)); 7362 WindowSP threads_window_sp( 7363 main_window_sp->CreateSubWindow("Threads", threads_bounds, false)); 7364 WindowSP status_window_sp( 7365 main_window_sp->CreateSubWindow("Status", status_bounds, false)); 7366 status_window_sp->SetCanBeActive( 7367 false); // Don't let the status bar become the active window 7368 main_window_sp->SetDelegate( 7369 std::static_pointer_cast<WindowDelegate>(app_delegate_sp)); 7370 source_window_sp->SetDelegate( 7371 WindowDelegateSP(new SourceFileWindowDelegate(m_debugger))); 7372 variables_window_sp->SetDelegate( 7373 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 7374 TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger)); 7375 threads_window_sp->SetDelegate(WindowDelegateSP( 7376 new TreeWindowDelegate(m_debugger, thread_delegate_sp))); 7377 status_window_sp->SetDelegate( 7378 WindowDelegateSP(new StatusBarWindowDelegate(m_debugger))); 7379 7380 // Show the main help window once the first time the curses GUI is 7381 // launched 7382 static bool g_showed_help = false; 7383 if (!g_showed_help) { 7384 g_showed_help = true; 7385 main_window_sp->CreateHelpSubwindow(); 7386 } 7387 7388 // All colors with black background. 7389 init_pair(1, COLOR_BLACK, COLOR_BLACK); 7390 init_pair(2, COLOR_RED, COLOR_BLACK); 7391 init_pair(3, COLOR_GREEN, COLOR_BLACK); 7392 init_pair(4, COLOR_YELLOW, COLOR_BLACK); 7393 init_pair(5, COLOR_BLUE, COLOR_BLACK); 7394 init_pair(6, COLOR_MAGENTA, COLOR_BLACK); 7395 init_pair(7, COLOR_CYAN, COLOR_BLACK); 7396 init_pair(8, COLOR_WHITE, COLOR_BLACK); 7397 // All colors with blue background. 7398 init_pair(9, COLOR_BLACK, COLOR_BLUE); 7399 init_pair(10, COLOR_RED, COLOR_BLUE); 7400 init_pair(11, COLOR_GREEN, COLOR_BLUE); 7401 init_pair(12, COLOR_YELLOW, COLOR_BLUE); 7402 init_pair(13, COLOR_BLUE, COLOR_BLUE); 7403 init_pair(14, COLOR_MAGENTA, COLOR_BLUE); 7404 init_pair(15, COLOR_CYAN, COLOR_BLUE); 7405 init_pair(16, COLOR_WHITE, COLOR_BLUE); 7406 // These must match the order in the color indexes enum. 7407 init_pair(17, COLOR_BLACK, COLOR_WHITE); 7408 init_pair(18, COLOR_MAGENTA, COLOR_WHITE); 7409 static_assert(LastColorPairIndex == 18, "Color indexes do not match."); 7410 7411 define_key("\033[Z", KEY_SHIFT_TAB); 7412 } 7413 } 7414 7415 void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); } 7416 7417 void IOHandlerCursesGUI::Run() { 7418 m_app_ap->Run(m_debugger); 7419 SetIsDone(true); 7420 } 7421 7422 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default; 7423 7424 void IOHandlerCursesGUI::Cancel() {} 7425 7426 bool IOHandlerCursesGUI::Interrupt() { return false; } 7427 7428 void IOHandlerCursesGUI::GotEOF() {} 7429 7430 void IOHandlerCursesGUI::TerminalSizeChanged() { 7431 m_app_ap->TerminalSizeChanged(); 7432 } 7433 7434 #endif // LLDB_ENABLE_CURSES 7435