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