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