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 #${frame.index}: {${function.name}${function.pc-offset}}}", 5020 m_format); 5021 } 5022 5023 ~FrameTreeDelegate() override = default; 5024 5025 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 5026 Thread *thread = (Thread *)item.GetUserData(); 5027 if (thread) { 5028 const uint64_t frame_idx = item.GetIdentifier(); 5029 StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx); 5030 if (frame_sp) { 5031 StreamString strm; 5032 const SymbolContext &sc = 5033 frame_sp->GetSymbolContext(eSymbolContextEverything); 5034 ExecutionContext exe_ctx(frame_sp); 5035 if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr, 5036 nullptr, false, false)) { 5037 int right_pad = 1; 5038 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 5039 } 5040 } 5041 } 5042 } 5043 5044 void TreeDelegateGenerateChildren(TreeItem &item) override { 5045 // No children for frames yet... 5046 } 5047 5048 bool TreeDelegateItemSelected(TreeItem &item) override { 5049 Thread *thread = (Thread *)item.GetUserData(); 5050 if (thread) { 5051 thread->GetProcess()->GetThreadList().SetSelectedThreadByID( 5052 thread->GetID()); 5053 const uint64_t frame_idx = item.GetIdentifier(); 5054 thread->SetSelectedFrameByIndex(frame_idx); 5055 return true; 5056 } 5057 return false; 5058 } 5059 5060 protected: 5061 FormatEntity::Entry m_format; 5062 }; 5063 5064 class ThreadTreeDelegate : public TreeDelegate { 5065 public: 5066 ThreadTreeDelegate(Debugger &debugger) 5067 : TreeDelegate(), m_debugger(debugger) { 5068 FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop " 5069 "reason = ${thread.stop-reason}}", 5070 m_format); 5071 } 5072 5073 ~ThreadTreeDelegate() override = default; 5074 5075 ProcessSP GetProcess() { 5076 return m_debugger.GetCommandInterpreter() 5077 .GetExecutionContext() 5078 .GetProcessSP(); 5079 } 5080 5081 ThreadSP GetThread(const TreeItem &item) { 5082 ProcessSP process_sp = GetProcess(); 5083 if (process_sp) 5084 return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier()); 5085 return ThreadSP(); 5086 } 5087 5088 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 5089 ThreadSP thread_sp = GetThread(item); 5090 if (thread_sp) { 5091 StreamString strm; 5092 ExecutionContext exe_ctx(thread_sp); 5093 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, 5094 nullptr, false, false)) { 5095 int right_pad = 1; 5096 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 5097 } 5098 } 5099 } 5100 5101 void TreeDelegateGenerateChildren(TreeItem &item) override { 5102 ProcessSP process_sp = GetProcess(); 5103 if (process_sp && process_sp->IsAlive()) { 5104 StateType state = process_sp->GetState(); 5105 if (StateIsStoppedState(state, true)) { 5106 ThreadSP thread_sp = GetThread(item); 5107 if (thread_sp) { 5108 if (m_stop_id == process_sp->GetStopID() && 5109 thread_sp->GetID() == m_tid) 5110 return; // Children are already up to date 5111 if (!m_frame_delegate_sp) { 5112 // Always expand the thread item the first time we show it 5113 m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>(); 5114 } 5115 5116 m_stop_id = process_sp->GetStopID(); 5117 m_tid = thread_sp->GetID(); 5118 5119 TreeItem t(&item, *m_frame_delegate_sp, false); 5120 size_t num_frames = thread_sp->GetStackFrameCount(); 5121 item.Resize(num_frames, t); 5122 for (size_t i = 0; i < num_frames; ++i) { 5123 item[i].SetUserData(thread_sp.get()); 5124 item[i].SetIdentifier(i); 5125 } 5126 } 5127 return; 5128 } 5129 } 5130 item.ClearChildren(); 5131 } 5132 5133 bool TreeDelegateItemSelected(TreeItem &item) override { 5134 ProcessSP process_sp = GetProcess(); 5135 if (process_sp && process_sp->IsAlive()) { 5136 StateType state = process_sp->GetState(); 5137 if (StateIsStoppedState(state, true)) { 5138 ThreadSP thread_sp = GetThread(item); 5139 if (thread_sp) { 5140 ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList(); 5141 std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex()); 5142 ThreadSP selected_thread_sp = thread_list.GetSelectedThread(); 5143 if (selected_thread_sp->GetID() != thread_sp->GetID()) { 5144 thread_list.SetSelectedThreadByID(thread_sp->GetID()); 5145 return true; 5146 } 5147 } 5148 } 5149 } 5150 return false; 5151 } 5152 5153 protected: 5154 Debugger &m_debugger; 5155 std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp; 5156 lldb::user_id_t m_tid = LLDB_INVALID_THREAD_ID; 5157 uint32_t m_stop_id = UINT32_MAX; 5158 FormatEntity::Entry m_format; 5159 }; 5160 5161 class ThreadsTreeDelegate : public TreeDelegate { 5162 public: 5163 ThreadsTreeDelegate(Debugger &debugger) 5164 : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger) { 5165 FormatEntity::Parse("process ${process.id}{, name = ${process.name}}", 5166 m_format); 5167 } 5168 5169 ~ThreadsTreeDelegate() override = default; 5170 5171 ProcessSP GetProcess() { 5172 return m_debugger.GetCommandInterpreter() 5173 .GetExecutionContext() 5174 .GetProcessSP(); 5175 } 5176 5177 bool TreeDelegateShouldDraw() override { 5178 ProcessSP process = GetProcess(); 5179 if (!process) 5180 return false; 5181 5182 if (StateIsRunningState(process->GetState())) 5183 return false; 5184 5185 return true; 5186 } 5187 5188 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 5189 ProcessSP process_sp = GetProcess(); 5190 if (process_sp && process_sp->IsAlive()) { 5191 StreamString strm; 5192 ExecutionContext exe_ctx(process_sp); 5193 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, 5194 nullptr, false, false)) { 5195 int right_pad = 1; 5196 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 5197 } 5198 } 5199 } 5200 5201 void TreeDelegateGenerateChildren(TreeItem &item) override { 5202 ProcessSP process_sp = GetProcess(); 5203 m_update_selection = false; 5204 if (process_sp && process_sp->IsAlive()) { 5205 StateType state = process_sp->GetState(); 5206 if (StateIsStoppedState(state, true)) { 5207 const uint32_t stop_id = process_sp->GetStopID(); 5208 if (m_stop_id == stop_id) 5209 return; // Children are already up to date 5210 5211 m_stop_id = stop_id; 5212 m_update_selection = true; 5213 5214 if (!m_thread_delegate_sp) { 5215 // Always expand the thread item the first time we show it 5216 // item.Expand(); 5217 m_thread_delegate_sp = 5218 std::make_shared<ThreadTreeDelegate>(m_debugger); 5219 } 5220 5221 TreeItem t(&item, *m_thread_delegate_sp, false); 5222 ThreadList &threads = process_sp->GetThreadList(); 5223 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 5224 ThreadSP selected_thread = threads.GetSelectedThread(); 5225 size_t num_threads = threads.GetSize(); 5226 item.Resize(num_threads, t); 5227 for (size_t i = 0; i < num_threads; ++i) { 5228 ThreadSP thread = threads.GetThreadAtIndex(i); 5229 item[i].SetIdentifier(thread->GetID()); 5230 item[i].SetMightHaveChildren(true); 5231 if (selected_thread->GetID() == thread->GetID()) 5232 item[i].Expand(); 5233 } 5234 return; 5235 } 5236 } 5237 item.ClearChildren(); 5238 } 5239 5240 void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index, 5241 TreeItem *&selected_item) override { 5242 if (!m_update_selection) 5243 return; 5244 5245 ProcessSP process_sp = GetProcess(); 5246 if (!(process_sp && process_sp->IsAlive())) 5247 return; 5248 5249 StateType state = process_sp->GetState(); 5250 if (!StateIsStoppedState(state, true)) 5251 return; 5252 5253 ThreadList &threads = process_sp->GetThreadList(); 5254 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 5255 ThreadSP selected_thread = threads.GetSelectedThread(); 5256 size_t num_threads = threads.GetSize(); 5257 for (size_t i = 0; i < num_threads; ++i) { 5258 ThreadSP thread = threads.GetThreadAtIndex(i); 5259 if (selected_thread->GetID() == thread->GetID()) { 5260 selected_item = &root[i][thread->GetSelectedFrameIndex()]; 5261 selection_index = selected_item->GetRowIndex(); 5262 return; 5263 } 5264 } 5265 } 5266 5267 bool TreeDelegateItemSelected(TreeItem &item) override { return false; } 5268 5269 bool TreeDelegateExpandRootByDefault() override { return true; } 5270 5271 protected: 5272 std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp; 5273 Debugger &m_debugger; 5274 uint32_t m_stop_id = UINT32_MAX; 5275 bool m_update_selection = false; 5276 FormatEntity::Entry m_format; 5277 }; 5278 5279 class BreakpointLocationTreeDelegate : public TreeDelegate { 5280 public: 5281 BreakpointLocationTreeDelegate(Debugger &debugger) 5282 : TreeDelegate(), m_debugger(debugger) {} 5283 5284 ~BreakpointLocationTreeDelegate() override = default; 5285 5286 Process *GetProcess() { 5287 ExecutionContext exe_ctx( 5288 m_debugger.GetCommandInterpreter().GetExecutionContext()); 5289 return exe_ctx.GetProcessPtr(); 5290 } 5291 5292 BreakpointLocationSP GetBreakpointLocation(const TreeItem &item) { 5293 Breakpoint *breakpoint = (Breakpoint *)item.GetUserData(); 5294 return breakpoint->GetLocationAtIndex(item.GetIdentifier()); 5295 } 5296 5297 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 5298 BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item); 5299 Process *process = GetProcess(); 5300 StreamString stream; 5301 stream.Printf("%i.%i: ", breakpoint_location->GetBreakpoint().GetID(), 5302 breakpoint_location->GetID()); 5303 Address address = breakpoint_location->GetAddress(); 5304 address.Dump(&stream, process, Address::DumpStyleResolvedDescription, 5305 Address::DumpStyleInvalid); 5306 window.PutCStringTruncated(1, stream.GetString().str().c_str()); 5307 } 5308 5309 StringList ComputeDetailsList(BreakpointLocationSP breakpoint_location) { 5310 StringList details; 5311 5312 Address address = breakpoint_location->GetAddress(); 5313 SymbolContext symbol_context; 5314 address.CalculateSymbolContext(&symbol_context); 5315 5316 if (symbol_context.module_sp) { 5317 StreamString module_stream; 5318 module_stream.PutCString("module = "); 5319 symbol_context.module_sp->GetFileSpec().Dump( 5320 module_stream.AsRawOstream()); 5321 details.AppendString(module_stream.GetString()); 5322 } 5323 5324 if (symbol_context.comp_unit != nullptr) { 5325 StreamString compile_unit_stream; 5326 compile_unit_stream.PutCString("compile unit = "); 5327 symbol_context.comp_unit->GetPrimaryFile().GetFilename().Dump( 5328 &compile_unit_stream); 5329 details.AppendString(compile_unit_stream.GetString()); 5330 5331 if (symbol_context.function != nullptr) { 5332 StreamString function_stream; 5333 function_stream.PutCString("function = "); 5334 function_stream.PutCString( 5335 symbol_context.function->GetName().AsCString("<unknown>")); 5336 details.AppendString(function_stream.GetString()); 5337 } 5338 5339 if (symbol_context.line_entry.line > 0) { 5340 StreamString location_stream; 5341 location_stream.PutCString("location = "); 5342 symbol_context.line_entry.DumpStopContext(&location_stream, true); 5343 details.AppendString(location_stream.GetString()); 5344 } 5345 5346 } else { 5347 if (symbol_context.symbol) { 5348 StreamString symbol_stream; 5349 if (breakpoint_location->IsReExported()) 5350 symbol_stream.PutCString("re-exported target = "); 5351 else 5352 symbol_stream.PutCString("symbol = "); 5353 symbol_stream.PutCString( 5354 symbol_context.symbol->GetName().AsCString("<unknown>")); 5355 details.AppendString(symbol_stream.GetString()); 5356 } 5357 } 5358 5359 Process *process = GetProcess(); 5360 5361 StreamString address_stream; 5362 address.Dump(&address_stream, process, Address::DumpStyleLoadAddress, 5363 Address::DumpStyleModuleWithFileAddress); 5364 details.AppendString(address_stream.GetString()); 5365 5366 BreakpointSiteSP breakpoint_site = breakpoint_location->GetBreakpointSite(); 5367 if (breakpoint_location->IsIndirect() && breakpoint_site) { 5368 Address resolved_address; 5369 resolved_address.SetLoadAddress(breakpoint_site->GetLoadAddress(), 5370 &breakpoint_location->GetTarget()); 5371 Symbol *resolved_symbol = resolved_address.CalculateSymbolContextSymbol(); 5372 if (resolved_symbol) { 5373 StreamString indirect_target_stream; 5374 indirect_target_stream.PutCString("indirect target = "); 5375 indirect_target_stream.PutCString( 5376 resolved_symbol->GetName().GetCString()); 5377 details.AppendString(indirect_target_stream.GetString()); 5378 } 5379 } 5380 5381 bool is_resolved = breakpoint_location->IsResolved(); 5382 StreamString resolved_stream; 5383 resolved_stream.Printf("resolved = %s", is_resolved ? "true" : "false"); 5384 details.AppendString(resolved_stream.GetString()); 5385 5386 bool is_hardware = is_resolved && breakpoint_site->IsHardware(); 5387 StreamString hardware_stream; 5388 hardware_stream.Printf("hardware = %s", is_hardware ? "true" : "false"); 5389 details.AppendString(hardware_stream.GetString()); 5390 5391 StreamString hit_count_stream; 5392 hit_count_stream.Printf("hit count = %-4u", 5393 breakpoint_location->GetHitCount()); 5394 details.AppendString(hit_count_stream.GetString()); 5395 5396 return details; 5397 } 5398 5399 void TreeDelegateGenerateChildren(TreeItem &item) override { 5400 BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item); 5401 StringList details = ComputeDetailsList(breakpoint_location); 5402 5403 if (!m_string_delegate_sp) 5404 m_string_delegate_sp = std::make_shared<TextTreeDelegate>(); 5405 TreeItem details_tree_item(&item, *m_string_delegate_sp, false); 5406 5407 item.Resize(details.GetSize(), details_tree_item); 5408 for (size_t i = 0; i < details.GetSize(); i++) { 5409 item[i].SetText(details.GetStringAtIndex(i)); 5410 } 5411 } 5412 5413 bool TreeDelegateItemSelected(TreeItem &item) override { return false; } 5414 5415 protected: 5416 Debugger &m_debugger; 5417 std::shared_ptr<TextTreeDelegate> m_string_delegate_sp; 5418 }; 5419 5420 class BreakpointTreeDelegate : public TreeDelegate { 5421 public: 5422 BreakpointTreeDelegate(Debugger &debugger) 5423 : TreeDelegate(), m_debugger(debugger), 5424 m_breakpoint_location_delegate_sp() {} 5425 5426 ~BreakpointTreeDelegate() override = default; 5427 5428 BreakpointSP GetBreakpoint(const TreeItem &item) { 5429 TargetSP target = m_debugger.GetSelectedTarget(); 5430 BreakpointList &breakpoints = target->GetBreakpointList(false); 5431 return breakpoints.GetBreakpointAtIndex(item.GetIdentifier()); 5432 } 5433 5434 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 5435 BreakpointSP breakpoint = GetBreakpoint(item); 5436 StreamString stream; 5437 stream.Format("{0}: ", breakpoint->GetID()); 5438 breakpoint->GetResolverDescription(&stream); 5439 breakpoint->GetFilterDescription(&stream); 5440 window.PutCStringTruncated(1, stream.GetString().str().c_str()); 5441 } 5442 5443 void TreeDelegateGenerateChildren(TreeItem &item) override { 5444 BreakpointSP breakpoint = GetBreakpoint(item); 5445 5446 if (!m_breakpoint_location_delegate_sp) 5447 m_breakpoint_location_delegate_sp = 5448 std::make_shared<BreakpointLocationTreeDelegate>(m_debugger); 5449 TreeItem breakpoint_location_tree_item( 5450 &item, *m_breakpoint_location_delegate_sp, true); 5451 5452 item.Resize(breakpoint->GetNumLocations(), breakpoint_location_tree_item); 5453 for (size_t i = 0; i < breakpoint->GetNumLocations(); i++) { 5454 item[i].SetIdentifier(i); 5455 item[i].SetUserData(breakpoint.get()); 5456 } 5457 } 5458 5459 bool TreeDelegateItemSelected(TreeItem &item) override { return false; } 5460 5461 protected: 5462 Debugger &m_debugger; 5463 std::shared_ptr<BreakpointLocationTreeDelegate> 5464 m_breakpoint_location_delegate_sp; 5465 }; 5466 5467 class BreakpointsTreeDelegate : public TreeDelegate { 5468 public: 5469 BreakpointsTreeDelegate(Debugger &debugger) 5470 : TreeDelegate(), m_debugger(debugger), m_breakpoint_delegate_sp() {} 5471 5472 ~BreakpointsTreeDelegate() override = default; 5473 5474 bool TreeDelegateShouldDraw() override { 5475 TargetSP target = m_debugger.GetSelectedTarget(); 5476 if (!target) 5477 return false; 5478 5479 return true; 5480 } 5481 5482 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 5483 window.PutCString("Breakpoints"); 5484 } 5485 5486 void TreeDelegateGenerateChildren(TreeItem &item) override { 5487 TargetSP target = m_debugger.GetSelectedTarget(); 5488 5489 BreakpointList &breakpoints = target->GetBreakpointList(false); 5490 std::unique_lock<std::recursive_mutex> lock; 5491 breakpoints.GetListMutex(lock); 5492 5493 if (!m_breakpoint_delegate_sp) 5494 m_breakpoint_delegate_sp = 5495 std::make_shared<BreakpointTreeDelegate>(m_debugger); 5496 TreeItem breakpoint_tree_item(&item, *m_breakpoint_delegate_sp, true); 5497 5498 item.Resize(breakpoints.GetSize(), breakpoint_tree_item); 5499 for (size_t i = 0; i < breakpoints.GetSize(); i++) { 5500 item[i].SetIdentifier(i); 5501 } 5502 } 5503 5504 bool TreeDelegateItemSelected(TreeItem &item) override { return false; } 5505 5506 bool TreeDelegateExpandRootByDefault() override { return true; } 5507 5508 protected: 5509 Debugger &m_debugger; 5510 std::shared_ptr<BreakpointTreeDelegate> m_breakpoint_delegate_sp; 5511 }; 5512 5513 class ValueObjectListDelegate : public WindowDelegate { 5514 public: 5515 ValueObjectListDelegate() : m_rows() {} 5516 5517 ValueObjectListDelegate(ValueObjectList &valobj_list) : m_rows() { 5518 SetValues(valobj_list); 5519 } 5520 5521 ~ValueObjectListDelegate() override = default; 5522 5523 void SetValues(ValueObjectList &valobj_list) { 5524 m_selected_row = nullptr; 5525 m_selected_row_idx = 0; 5526 m_first_visible_row = 0; 5527 m_num_rows = 0; 5528 m_rows.clear(); 5529 for (auto &valobj_sp : valobj_list.GetObjects()) 5530 m_rows.push_back(Row(valobj_sp, nullptr)); 5531 } 5532 5533 bool WindowDelegateDraw(Window &window, bool force) override { 5534 m_num_rows = 0; 5535 m_min_x = 2; 5536 m_min_y = 1; 5537 m_max_x = window.GetWidth() - 1; 5538 m_max_y = window.GetHeight() - 1; 5539 5540 window.Erase(); 5541 window.DrawTitleBox(window.GetName()); 5542 5543 const int num_visible_rows = NumVisibleRows(); 5544 const int num_rows = CalculateTotalNumberRows(m_rows); 5545 5546 // If we unexpanded while having something selected our total number of 5547 // rows is less than the num visible rows, then make sure we show all the 5548 // rows by setting the first visible row accordingly. 5549 if (m_first_visible_row > 0 && num_rows < num_visible_rows) 5550 m_first_visible_row = 0; 5551 5552 // Make sure the selected row is always visible 5553 if (m_selected_row_idx < m_first_visible_row) 5554 m_first_visible_row = m_selected_row_idx; 5555 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) 5556 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; 5557 5558 DisplayRows(window, m_rows, g_options); 5559 5560 // Get the selected row 5561 m_selected_row = GetRowForRowIndex(m_selected_row_idx); 5562 // Keep the cursor on the selected row so the highlight and the cursor are 5563 // always on the same line 5564 if (m_selected_row) 5565 window.MoveCursor(m_selected_row->x, m_selected_row->y); 5566 5567 return true; // Drawing handled 5568 } 5569 5570 KeyHelp *WindowDelegateGetKeyHelp() override { 5571 static curses::KeyHelp g_source_view_key_help[] = { 5572 {KEY_UP, "Select previous item"}, 5573 {KEY_DOWN, "Select next item"}, 5574 {KEY_RIGHT, "Expand selected item"}, 5575 {KEY_LEFT, "Unexpand selected item or select parent if not expanded"}, 5576 {KEY_PPAGE, "Page up"}, 5577 {KEY_NPAGE, "Page down"}, 5578 {'A', "Format as annotated address"}, 5579 {'b', "Format as binary"}, 5580 {'B', "Format as hex bytes with ASCII"}, 5581 {'c', "Format as character"}, 5582 {'d', "Format as a signed integer"}, 5583 {'D', "Format selected value using the default format for the type"}, 5584 {'f', "Format as float"}, 5585 {'h', "Show help dialog"}, 5586 {'i', "Format as instructions"}, 5587 {'o', "Format as octal"}, 5588 {'p', "Format as pointer"}, 5589 {'s', "Format as C string"}, 5590 {'t', "Toggle showing/hiding type names"}, 5591 {'u', "Format as an unsigned integer"}, 5592 {'x', "Format as hex"}, 5593 {'X', "Format as uppercase hex"}, 5594 {' ', "Toggle item expansion"}, 5595 {',', "Page up"}, 5596 {'.', "Page down"}, 5597 {'\0', nullptr}}; 5598 return g_source_view_key_help; 5599 } 5600 5601 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 5602 switch (c) { 5603 case 'x': 5604 case 'X': 5605 case 'o': 5606 case 's': 5607 case 'u': 5608 case 'd': 5609 case 'D': 5610 case 'i': 5611 case 'A': 5612 case 'p': 5613 case 'c': 5614 case 'b': 5615 case 'B': 5616 case 'f': 5617 // Change the format for the currently selected item 5618 if (m_selected_row) { 5619 auto valobj_sp = m_selected_row->value.GetSP(); 5620 if (valobj_sp) 5621 valobj_sp->SetFormat(FormatForChar(c)); 5622 } 5623 return eKeyHandled; 5624 5625 case 't': 5626 // Toggle showing type names 5627 g_options.show_types = !g_options.show_types; 5628 return eKeyHandled; 5629 5630 case ',': 5631 case KEY_PPAGE: 5632 // Page up key 5633 if (m_first_visible_row > 0) { 5634 if (static_cast<int>(m_first_visible_row) > m_max_y) 5635 m_first_visible_row -= m_max_y; 5636 else 5637 m_first_visible_row = 0; 5638 m_selected_row_idx = m_first_visible_row; 5639 } 5640 return eKeyHandled; 5641 5642 case '.': 5643 case KEY_NPAGE: 5644 // Page down key 5645 if (m_num_rows > static_cast<size_t>(m_max_y)) { 5646 if (m_first_visible_row + m_max_y < m_num_rows) { 5647 m_first_visible_row += m_max_y; 5648 m_selected_row_idx = m_first_visible_row; 5649 } 5650 } 5651 return eKeyHandled; 5652 5653 case KEY_UP: 5654 if (m_selected_row_idx > 0) 5655 --m_selected_row_idx; 5656 return eKeyHandled; 5657 5658 case KEY_DOWN: 5659 if (m_selected_row_idx + 1 < m_num_rows) 5660 ++m_selected_row_idx; 5661 return eKeyHandled; 5662 5663 case KEY_RIGHT: 5664 if (m_selected_row) { 5665 if (!m_selected_row->expanded) 5666 m_selected_row->Expand(); 5667 } 5668 return eKeyHandled; 5669 5670 case KEY_LEFT: 5671 if (m_selected_row) { 5672 if (m_selected_row->expanded) 5673 m_selected_row->Unexpand(); 5674 else if (m_selected_row->parent) 5675 m_selected_row_idx = m_selected_row->parent->row_idx; 5676 } 5677 return eKeyHandled; 5678 5679 case ' ': 5680 // Toggle expansion state when SPACE is pressed 5681 if (m_selected_row) { 5682 if (m_selected_row->expanded) 5683 m_selected_row->Unexpand(); 5684 else 5685 m_selected_row->Expand(); 5686 } 5687 return eKeyHandled; 5688 5689 case 'h': 5690 window.CreateHelpSubwindow(); 5691 return eKeyHandled; 5692 5693 default: 5694 break; 5695 } 5696 return eKeyNotHandled; 5697 } 5698 5699 protected: 5700 std::vector<Row> m_rows; 5701 Row *m_selected_row = nullptr; 5702 uint32_t m_selected_row_idx = 0; 5703 uint32_t m_first_visible_row = 0; 5704 uint32_t m_num_rows = 0; 5705 int m_min_x; 5706 int m_min_y; 5707 int m_max_x = 0; 5708 int m_max_y = 0; 5709 5710 static Format FormatForChar(int c) { 5711 switch (c) { 5712 case 'x': 5713 return eFormatHex; 5714 case 'X': 5715 return eFormatHexUppercase; 5716 case 'o': 5717 return eFormatOctal; 5718 case 's': 5719 return eFormatCString; 5720 case 'u': 5721 return eFormatUnsigned; 5722 case 'd': 5723 return eFormatDecimal; 5724 case 'D': 5725 return eFormatDefault; 5726 case 'i': 5727 return eFormatInstruction; 5728 case 'A': 5729 return eFormatAddressInfo; 5730 case 'p': 5731 return eFormatPointer; 5732 case 'c': 5733 return eFormatChar; 5734 case 'b': 5735 return eFormatBinary; 5736 case 'B': 5737 return eFormatBytesWithASCII; 5738 case 'f': 5739 return eFormatFloat; 5740 } 5741 return eFormatDefault; 5742 } 5743 5744 bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options, 5745 bool highlight, bool last_child) { 5746 ValueObject *valobj = row.value.GetSP().get(); 5747 5748 if (valobj == nullptr) 5749 return false; 5750 5751 const char *type_name = 5752 options.show_types ? valobj->GetTypeName().GetCString() : nullptr; 5753 const char *name = valobj->GetName().GetCString(); 5754 const char *value = valobj->GetValueAsCString(); 5755 const char *summary = valobj->GetSummaryAsCString(); 5756 5757 window.MoveCursor(row.x, row.y); 5758 5759 row.DrawTree(window); 5760 5761 if (highlight) 5762 window.AttributeOn(A_REVERSE); 5763 5764 if (type_name && type_name[0]) 5765 window.PrintfTruncated(1, "(%s) ", type_name); 5766 5767 if (name && name[0]) 5768 window.PutCStringTruncated(1, name); 5769 5770 attr_t changd_attr = 0; 5771 if (valobj->GetValueDidChange()) 5772 changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD; 5773 5774 if (value && value[0]) { 5775 window.PutCStringTruncated(1, " = "); 5776 if (changd_attr) 5777 window.AttributeOn(changd_attr); 5778 window.PutCStringTruncated(1, value); 5779 if (changd_attr) 5780 window.AttributeOff(changd_attr); 5781 } 5782 5783 if (summary && summary[0]) { 5784 window.PutCStringTruncated(1, " "); 5785 if (changd_attr) 5786 window.AttributeOn(changd_attr); 5787 window.PutCStringTruncated(1, summary); 5788 if (changd_attr) 5789 window.AttributeOff(changd_attr); 5790 } 5791 5792 if (highlight) 5793 window.AttributeOff(A_REVERSE); 5794 5795 return true; 5796 } 5797 5798 void DisplayRows(Window &window, std::vector<Row> &rows, 5799 DisplayOptions &options) { 5800 // > 0x25B7 5801 // \/ 0x25BD 5802 5803 bool window_is_active = window.IsActive(); 5804 for (auto &row : rows) { 5805 const bool last_child = row.parent && &rows[rows.size() - 1] == &row; 5806 // Save the row index in each Row structure 5807 row.row_idx = m_num_rows; 5808 if ((m_num_rows >= m_first_visible_row) && 5809 ((m_num_rows - m_first_visible_row) < 5810 static_cast<size_t>(NumVisibleRows()))) { 5811 row.x = m_min_x; 5812 row.y = m_num_rows - m_first_visible_row + 1; 5813 if (DisplayRowObject(window, row, options, 5814 window_is_active && 5815 m_num_rows == m_selected_row_idx, 5816 last_child)) { 5817 ++m_num_rows; 5818 } else { 5819 row.x = 0; 5820 row.y = 0; 5821 } 5822 } else { 5823 row.x = 0; 5824 row.y = 0; 5825 ++m_num_rows; 5826 } 5827 5828 auto &children = row.GetChildren(); 5829 if (row.expanded && !children.empty()) { 5830 DisplayRows(window, children, options); 5831 } 5832 } 5833 } 5834 5835 int CalculateTotalNumberRows(std::vector<Row> &rows) { 5836 int row_count = 0; 5837 for (auto &row : rows) { 5838 ++row_count; 5839 if (row.expanded) 5840 row_count += CalculateTotalNumberRows(row.GetChildren()); 5841 } 5842 return row_count; 5843 } 5844 5845 static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) { 5846 for (auto &row : rows) { 5847 if (row_index == 0) 5848 return &row; 5849 else { 5850 --row_index; 5851 auto &children = row.GetChildren(); 5852 if (row.expanded && !children.empty()) { 5853 Row *result = GetRowForRowIndexImpl(children, row_index); 5854 if (result) 5855 return result; 5856 } 5857 } 5858 } 5859 return nullptr; 5860 } 5861 5862 Row *GetRowForRowIndex(size_t row_index) { 5863 return GetRowForRowIndexImpl(m_rows, row_index); 5864 } 5865 5866 int NumVisibleRows() const { return m_max_y - m_min_y; } 5867 5868 static DisplayOptions g_options; 5869 }; 5870 5871 class FrameVariablesWindowDelegate : public ValueObjectListDelegate { 5872 public: 5873 FrameVariablesWindowDelegate(Debugger &debugger) 5874 : ValueObjectListDelegate(), m_debugger(debugger) {} 5875 5876 ~FrameVariablesWindowDelegate() override = default; 5877 5878 const char *WindowDelegateGetHelpText() override { 5879 return "Frame variable window keyboard shortcuts:"; 5880 } 5881 5882 bool WindowDelegateDraw(Window &window, bool force) override { 5883 ExecutionContext exe_ctx( 5884 m_debugger.GetCommandInterpreter().GetExecutionContext()); 5885 Process *process = exe_ctx.GetProcessPtr(); 5886 Block *frame_block = nullptr; 5887 StackFrame *frame = nullptr; 5888 5889 if (process) { 5890 StateType state = process->GetState(); 5891 if (StateIsStoppedState(state, true)) { 5892 frame = exe_ctx.GetFramePtr(); 5893 if (frame) 5894 frame_block = frame->GetFrameBlock(); 5895 } else if (StateIsRunningState(state)) { 5896 return true; // Don't do any updating when we are running 5897 } 5898 } 5899 5900 ValueObjectList local_values; 5901 if (frame_block) { 5902 // Only update the variables if they have changed 5903 if (m_frame_block != frame_block) { 5904 m_frame_block = frame_block; 5905 5906 VariableList *locals = frame->GetVariableList(true); 5907 if (locals) { 5908 const DynamicValueType use_dynamic = eDynamicDontRunTarget; 5909 for (const VariableSP &local_sp : *locals) { 5910 ValueObjectSP value_sp = 5911 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic); 5912 if (value_sp) { 5913 ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue(); 5914 if (synthetic_value_sp) 5915 local_values.Append(synthetic_value_sp); 5916 else 5917 local_values.Append(value_sp); 5918 } 5919 } 5920 // Update the values 5921 SetValues(local_values); 5922 } 5923 } 5924 } else { 5925 m_frame_block = nullptr; 5926 // Update the values with an empty list if there is no frame 5927 SetValues(local_values); 5928 } 5929 5930 return ValueObjectListDelegate::WindowDelegateDraw(window, force); 5931 } 5932 5933 protected: 5934 Debugger &m_debugger; 5935 Block *m_frame_block = nullptr; 5936 }; 5937 5938 class RegistersWindowDelegate : public ValueObjectListDelegate { 5939 public: 5940 RegistersWindowDelegate(Debugger &debugger) 5941 : ValueObjectListDelegate(), m_debugger(debugger) {} 5942 5943 ~RegistersWindowDelegate() override = default; 5944 5945 const char *WindowDelegateGetHelpText() override { 5946 return "Register window keyboard shortcuts:"; 5947 } 5948 5949 bool WindowDelegateDraw(Window &window, bool force) override { 5950 ExecutionContext exe_ctx( 5951 m_debugger.GetCommandInterpreter().GetExecutionContext()); 5952 StackFrame *frame = exe_ctx.GetFramePtr(); 5953 5954 ValueObjectList value_list; 5955 if (frame) { 5956 if (frame->GetStackID() != m_stack_id) { 5957 m_stack_id = frame->GetStackID(); 5958 RegisterContextSP reg_ctx(frame->GetRegisterContext()); 5959 if (reg_ctx) { 5960 const uint32_t num_sets = reg_ctx->GetRegisterSetCount(); 5961 for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) { 5962 value_list.Append( 5963 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx)); 5964 } 5965 } 5966 SetValues(value_list); 5967 } 5968 } else { 5969 Process *process = exe_ctx.GetProcessPtr(); 5970 if (process && process->IsAlive()) 5971 return true; // Don't do any updating if we are running 5972 else { 5973 // Update the values with an empty list if there is no process or the 5974 // process isn't alive anymore 5975 SetValues(value_list); 5976 } 5977 } 5978 return ValueObjectListDelegate::WindowDelegateDraw(window, force); 5979 } 5980 5981 protected: 5982 Debugger &m_debugger; 5983 StackID m_stack_id; 5984 }; 5985 5986 static const char *CursesKeyToCString(int ch) { 5987 static char g_desc[32]; 5988 if (ch >= KEY_F0 && ch < KEY_F0 + 64) { 5989 snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0); 5990 return g_desc; 5991 } 5992 switch (ch) { 5993 case KEY_DOWN: 5994 return "down"; 5995 case KEY_UP: 5996 return "up"; 5997 case KEY_LEFT: 5998 return "left"; 5999 case KEY_RIGHT: 6000 return "right"; 6001 case KEY_HOME: 6002 return "home"; 6003 case KEY_BACKSPACE: 6004 return "backspace"; 6005 case KEY_DL: 6006 return "delete-line"; 6007 case KEY_IL: 6008 return "insert-line"; 6009 case KEY_DC: 6010 return "delete-char"; 6011 case KEY_IC: 6012 return "insert-char"; 6013 case KEY_CLEAR: 6014 return "clear"; 6015 case KEY_EOS: 6016 return "clear-to-eos"; 6017 case KEY_EOL: 6018 return "clear-to-eol"; 6019 case KEY_SF: 6020 return "scroll-forward"; 6021 case KEY_SR: 6022 return "scroll-backward"; 6023 case KEY_NPAGE: 6024 return "page-down"; 6025 case KEY_PPAGE: 6026 return "page-up"; 6027 case KEY_STAB: 6028 return "set-tab"; 6029 case KEY_CTAB: 6030 return "clear-tab"; 6031 case KEY_CATAB: 6032 return "clear-all-tabs"; 6033 case KEY_ENTER: 6034 return "enter"; 6035 case KEY_PRINT: 6036 return "print"; 6037 case KEY_LL: 6038 return "lower-left key"; 6039 case KEY_A1: 6040 return "upper left of keypad"; 6041 case KEY_A3: 6042 return "upper right of keypad"; 6043 case KEY_B2: 6044 return "center of keypad"; 6045 case KEY_C1: 6046 return "lower left of keypad"; 6047 case KEY_C3: 6048 return "lower right of keypad"; 6049 case KEY_BTAB: 6050 return "back-tab key"; 6051 case KEY_BEG: 6052 return "begin key"; 6053 case KEY_CANCEL: 6054 return "cancel key"; 6055 case KEY_CLOSE: 6056 return "close key"; 6057 case KEY_COMMAND: 6058 return "command key"; 6059 case KEY_COPY: 6060 return "copy key"; 6061 case KEY_CREATE: 6062 return "create key"; 6063 case KEY_END: 6064 return "end key"; 6065 case KEY_EXIT: 6066 return "exit key"; 6067 case KEY_FIND: 6068 return "find key"; 6069 case KEY_HELP: 6070 return "help key"; 6071 case KEY_MARK: 6072 return "mark key"; 6073 case KEY_MESSAGE: 6074 return "message key"; 6075 case KEY_MOVE: 6076 return "move key"; 6077 case KEY_NEXT: 6078 return "next key"; 6079 case KEY_OPEN: 6080 return "open key"; 6081 case KEY_OPTIONS: 6082 return "options key"; 6083 case KEY_PREVIOUS: 6084 return "previous key"; 6085 case KEY_REDO: 6086 return "redo key"; 6087 case KEY_REFERENCE: 6088 return "reference key"; 6089 case KEY_REFRESH: 6090 return "refresh key"; 6091 case KEY_REPLACE: 6092 return "replace key"; 6093 case KEY_RESTART: 6094 return "restart key"; 6095 case KEY_RESUME: 6096 return "resume key"; 6097 case KEY_SAVE: 6098 return "save key"; 6099 case KEY_SBEG: 6100 return "shifted begin key"; 6101 case KEY_SCANCEL: 6102 return "shifted cancel key"; 6103 case KEY_SCOMMAND: 6104 return "shifted command key"; 6105 case KEY_SCOPY: 6106 return "shifted copy key"; 6107 case KEY_SCREATE: 6108 return "shifted create key"; 6109 case KEY_SDC: 6110 return "shifted delete-character key"; 6111 case KEY_SDL: 6112 return "shifted delete-line key"; 6113 case KEY_SELECT: 6114 return "select key"; 6115 case KEY_SEND: 6116 return "shifted end key"; 6117 case KEY_SEOL: 6118 return "shifted clear-to-end-of-line key"; 6119 case KEY_SEXIT: 6120 return "shifted exit key"; 6121 case KEY_SFIND: 6122 return "shifted find key"; 6123 case KEY_SHELP: 6124 return "shifted help key"; 6125 case KEY_SHOME: 6126 return "shifted home key"; 6127 case KEY_SIC: 6128 return "shifted insert-character key"; 6129 case KEY_SLEFT: 6130 return "shifted left-arrow key"; 6131 case KEY_SMESSAGE: 6132 return "shifted message key"; 6133 case KEY_SMOVE: 6134 return "shifted move key"; 6135 case KEY_SNEXT: 6136 return "shifted next key"; 6137 case KEY_SOPTIONS: 6138 return "shifted options key"; 6139 case KEY_SPREVIOUS: 6140 return "shifted previous key"; 6141 case KEY_SPRINT: 6142 return "shifted print key"; 6143 case KEY_SREDO: 6144 return "shifted redo key"; 6145 case KEY_SREPLACE: 6146 return "shifted replace key"; 6147 case KEY_SRIGHT: 6148 return "shifted right-arrow key"; 6149 case KEY_SRSUME: 6150 return "shifted resume key"; 6151 case KEY_SSAVE: 6152 return "shifted save key"; 6153 case KEY_SSUSPEND: 6154 return "shifted suspend key"; 6155 case KEY_SUNDO: 6156 return "shifted undo key"; 6157 case KEY_SUSPEND: 6158 return "suspend key"; 6159 case KEY_UNDO: 6160 return "undo key"; 6161 case KEY_MOUSE: 6162 return "Mouse event has occurred"; 6163 case KEY_RESIZE: 6164 return "Terminal resize event"; 6165 #ifdef KEY_EVENT 6166 case KEY_EVENT: 6167 return "We were interrupted by an event"; 6168 #endif 6169 case KEY_RETURN: 6170 return "return"; 6171 case ' ': 6172 return "space"; 6173 case '\t': 6174 return "tab"; 6175 case KEY_ESCAPE: 6176 return "escape"; 6177 default: 6178 if (llvm::isPrint(ch)) 6179 snprintf(g_desc, sizeof(g_desc), "%c", ch); 6180 else 6181 snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch); 6182 return g_desc; 6183 } 6184 return nullptr; 6185 } 6186 6187 HelpDialogDelegate::HelpDialogDelegate(const char *text, 6188 KeyHelp *key_help_array) 6189 : m_text() { 6190 if (text && text[0]) { 6191 m_text.SplitIntoLines(text); 6192 m_text.AppendString(""); 6193 } 6194 if (key_help_array) { 6195 for (KeyHelp *key = key_help_array; key->ch; ++key) { 6196 StreamString key_description; 6197 key_description.Printf("%10s - %s", CursesKeyToCString(key->ch), 6198 key->description); 6199 m_text.AppendString(key_description.GetString()); 6200 } 6201 } 6202 } 6203 6204 HelpDialogDelegate::~HelpDialogDelegate() = default; 6205 6206 bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) { 6207 window.Erase(); 6208 const int window_height = window.GetHeight(); 6209 int x = 2; 6210 int y = 1; 6211 const int min_y = y; 6212 const int max_y = window_height - 1 - y; 6213 const size_t num_visible_lines = max_y - min_y + 1; 6214 const size_t num_lines = m_text.GetSize(); 6215 const char *bottom_message; 6216 if (num_lines <= num_visible_lines) 6217 bottom_message = "Press any key to exit"; 6218 else 6219 bottom_message = "Use arrows to scroll, any other key to exit"; 6220 window.DrawTitleBox(window.GetName(), bottom_message); 6221 while (y <= max_y) { 6222 window.MoveCursor(x, y); 6223 window.PutCStringTruncated( 6224 1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y)); 6225 ++y; 6226 } 6227 return true; 6228 } 6229 6230 HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window, 6231 int key) { 6232 bool done = false; 6233 const size_t num_lines = m_text.GetSize(); 6234 const size_t num_visible_lines = window.GetHeight() - 2; 6235 6236 if (num_lines <= num_visible_lines) { 6237 done = true; 6238 // If we have all lines visible and don't need scrolling, then any key 6239 // press will cause us to exit 6240 } else { 6241 switch (key) { 6242 case KEY_UP: 6243 if (m_first_visible_line > 0) 6244 --m_first_visible_line; 6245 break; 6246 6247 case KEY_DOWN: 6248 if (m_first_visible_line + num_visible_lines < num_lines) 6249 ++m_first_visible_line; 6250 break; 6251 6252 case KEY_PPAGE: 6253 case ',': 6254 if (m_first_visible_line > 0) { 6255 if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines) 6256 m_first_visible_line -= num_visible_lines; 6257 else 6258 m_first_visible_line = 0; 6259 } 6260 break; 6261 6262 case KEY_NPAGE: 6263 case '.': 6264 if (m_first_visible_line + num_visible_lines < num_lines) { 6265 m_first_visible_line += num_visible_lines; 6266 if (static_cast<size_t>(m_first_visible_line) > num_lines) 6267 m_first_visible_line = num_lines - num_visible_lines; 6268 } 6269 break; 6270 6271 default: 6272 done = true; 6273 break; 6274 } 6275 } 6276 if (done) 6277 window.GetParent()->RemoveSubWindow(&window); 6278 return eKeyHandled; 6279 } 6280 6281 class ApplicationDelegate : public WindowDelegate, public MenuDelegate { 6282 public: 6283 enum { 6284 eMenuID_LLDB = 1, 6285 eMenuID_LLDBAbout, 6286 eMenuID_LLDBExit, 6287 6288 eMenuID_Target, 6289 eMenuID_TargetCreate, 6290 eMenuID_TargetDelete, 6291 6292 eMenuID_Process, 6293 eMenuID_ProcessAttach, 6294 eMenuID_ProcessDetachResume, 6295 eMenuID_ProcessDetachSuspended, 6296 eMenuID_ProcessLaunch, 6297 eMenuID_ProcessContinue, 6298 eMenuID_ProcessHalt, 6299 eMenuID_ProcessKill, 6300 6301 eMenuID_Thread, 6302 eMenuID_ThreadStepIn, 6303 eMenuID_ThreadStepOver, 6304 eMenuID_ThreadStepOut, 6305 6306 eMenuID_View, 6307 eMenuID_ViewBacktrace, 6308 eMenuID_ViewRegisters, 6309 eMenuID_ViewSource, 6310 eMenuID_ViewVariables, 6311 eMenuID_ViewBreakpoints, 6312 6313 eMenuID_Help, 6314 eMenuID_HelpGUIHelp 6315 }; 6316 6317 ApplicationDelegate(Application &app, Debugger &debugger) 6318 : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {} 6319 6320 ~ApplicationDelegate() override = default; 6321 6322 bool WindowDelegateDraw(Window &window, bool force) override { 6323 return false; // Drawing not handled, let standard window drawing happen 6324 } 6325 6326 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { 6327 switch (key) { 6328 case '\t': 6329 window.SelectNextWindowAsActive(); 6330 return eKeyHandled; 6331 6332 case KEY_SHIFT_TAB: 6333 window.SelectPreviousWindowAsActive(); 6334 return eKeyHandled; 6335 6336 case 'h': 6337 window.CreateHelpSubwindow(); 6338 return eKeyHandled; 6339 6340 case KEY_ESCAPE: 6341 return eQuitApplication; 6342 6343 default: 6344 break; 6345 } 6346 return eKeyNotHandled; 6347 } 6348 6349 const char *WindowDelegateGetHelpText() override { 6350 return "Welcome to the LLDB curses GUI.\n\n" 6351 "Press the TAB key to change the selected view.\n" 6352 "Each view has its own keyboard shortcuts, press 'h' to open a " 6353 "dialog to display them.\n\n" 6354 "Common key bindings for all views:"; 6355 } 6356 6357 KeyHelp *WindowDelegateGetKeyHelp() override { 6358 static curses::KeyHelp g_source_view_key_help[] = { 6359 {'\t', "Select next view"}, 6360 {KEY_BTAB, "Select previous view"}, 6361 {'h', "Show help dialog with view specific key bindings"}, 6362 {',', "Page up"}, 6363 {'.', "Page down"}, 6364 {KEY_UP, "Select previous"}, 6365 {KEY_DOWN, "Select next"}, 6366 {KEY_LEFT, "Unexpand or select parent"}, 6367 {KEY_RIGHT, "Expand"}, 6368 {KEY_PPAGE, "Page up"}, 6369 {KEY_NPAGE, "Page down"}, 6370 {'\0', nullptr}}; 6371 return g_source_view_key_help; 6372 } 6373 6374 MenuActionResult MenuDelegateAction(Menu &menu) override { 6375 switch (menu.GetIdentifier()) { 6376 case eMenuID_TargetCreate: { 6377 WindowSP main_window_sp = m_app.GetMainWindow(); 6378 FormDelegateSP form_delegate_sp = 6379 FormDelegateSP(new TargetCreateFormDelegate(m_debugger)); 6380 Rect bounds = main_window_sp->GetCenteredRect(80, 19); 6381 WindowSP form_window_sp = main_window_sp->CreateSubWindow( 6382 form_delegate_sp->GetName().c_str(), bounds, true); 6383 WindowDelegateSP window_delegate_sp = 6384 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); 6385 form_window_sp->SetDelegate(window_delegate_sp); 6386 return MenuActionResult::Handled; 6387 } 6388 case eMenuID_ThreadStepIn: { 6389 ExecutionContext exe_ctx = 6390 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6391 if (exe_ctx.HasThreadScope()) { 6392 Process *process = exe_ctx.GetProcessPtr(); 6393 if (process && process->IsAlive() && 6394 StateIsStoppedState(process->GetState(), true)) 6395 exe_ctx.GetThreadRef().StepIn(true); 6396 } 6397 } 6398 return MenuActionResult::Handled; 6399 6400 case eMenuID_ThreadStepOut: { 6401 ExecutionContext exe_ctx = 6402 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6403 if (exe_ctx.HasThreadScope()) { 6404 Process *process = exe_ctx.GetProcessPtr(); 6405 if (process && process->IsAlive() && 6406 StateIsStoppedState(process->GetState(), true)) 6407 exe_ctx.GetThreadRef().StepOut(); 6408 } 6409 } 6410 return MenuActionResult::Handled; 6411 6412 case eMenuID_ThreadStepOver: { 6413 ExecutionContext exe_ctx = 6414 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6415 if (exe_ctx.HasThreadScope()) { 6416 Process *process = exe_ctx.GetProcessPtr(); 6417 if (process && process->IsAlive() && 6418 StateIsStoppedState(process->GetState(), true)) 6419 exe_ctx.GetThreadRef().StepOver(true); 6420 } 6421 } 6422 return MenuActionResult::Handled; 6423 6424 case eMenuID_ProcessAttach: { 6425 WindowSP main_window_sp = m_app.GetMainWindow(); 6426 FormDelegateSP form_delegate_sp = FormDelegateSP( 6427 new ProcessAttachFormDelegate(m_debugger, main_window_sp)); 6428 Rect bounds = main_window_sp->GetCenteredRect(80, 22); 6429 WindowSP form_window_sp = main_window_sp->CreateSubWindow( 6430 form_delegate_sp->GetName().c_str(), bounds, true); 6431 WindowDelegateSP window_delegate_sp = 6432 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); 6433 form_window_sp->SetDelegate(window_delegate_sp); 6434 return MenuActionResult::Handled; 6435 } 6436 case eMenuID_ProcessLaunch: { 6437 WindowSP main_window_sp = m_app.GetMainWindow(); 6438 FormDelegateSP form_delegate_sp = FormDelegateSP( 6439 new ProcessLaunchFormDelegate(m_debugger, main_window_sp)); 6440 Rect bounds = main_window_sp->GetCenteredRect(80, 22); 6441 WindowSP form_window_sp = main_window_sp->CreateSubWindow( 6442 form_delegate_sp->GetName().c_str(), bounds, true); 6443 WindowDelegateSP window_delegate_sp = 6444 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); 6445 form_window_sp->SetDelegate(window_delegate_sp); 6446 return MenuActionResult::Handled; 6447 } 6448 6449 case eMenuID_ProcessContinue: { 6450 ExecutionContext exe_ctx = 6451 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6452 if (exe_ctx.HasProcessScope()) { 6453 Process *process = exe_ctx.GetProcessPtr(); 6454 if (process && process->IsAlive() && 6455 StateIsStoppedState(process->GetState(), true)) 6456 process->Resume(); 6457 } 6458 } 6459 return MenuActionResult::Handled; 6460 6461 case eMenuID_ProcessKill: { 6462 ExecutionContext exe_ctx = 6463 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6464 if (exe_ctx.HasProcessScope()) { 6465 Process *process = exe_ctx.GetProcessPtr(); 6466 if (process && process->IsAlive()) 6467 process->Destroy(false); 6468 } 6469 } 6470 return MenuActionResult::Handled; 6471 6472 case eMenuID_ProcessHalt: { 6473 ExecutionContext exe_ctx = 6474 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6475 if (exe_ctx.HasProcessScope()) { 6476 Process *process = exe_ctx.GetProcessPtr(); 6477 if (process && process->IsAlive()) 6478 process->Halt(); 6479 } 6480 } 6481 return MenuActionResult::Handled; 6482 6483 case eMenuID_ProcessDetachResume: 6484 case eMenuID_ProcessDetachSuspended: { 6485 ExecutionContext exe_ctx = 6486 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6487 if (exe_ctx.HasProcessScope()) { 6488 Process *process = exe_ctx.GetProcessPtr(); 6489 if (process && process->IsAlive()) 6490 process->Detach(menu.GetIdentifier() == 6491 eMenuID_ProcessDetachSuspended); 6492 } 6493 } 6494 return MenuActionResult::Handled; 6495 6496 case eMenuID_Process: { 6497 // Populate the menu with all of the threads if the process is stopped 6498 // when the Process menu gets selected and is about to display its 6499 // submenu. 6500 Menus &submenus = menu.GetSubmenus(); 6501 ExecutionContext exe_ctx = 6502 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6503 Process *process = exe_ctx.GetProcessPtr(); 6504 if (process && process->IsAlive() && 6505 StateIsStoppedState(process->GetState(), true)) { 6506 if (submenus.size() == 7) 6507 menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 6508 else if (submenus.size() > 8) 6509 submenus.erase(submenus.begin() + 8, submenus.end()); 6510 6511 ThreadList &threads = process->GetThreadList(); 6512 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 6513 size_t num_threads = threads.GetSize(); 6514 for (size_t i = 0; i < num_threads; ++i) { 6515 ThreadSP thread_sp = threads.GetThreadAtIndex(i); 6516 char menu_char = '\0'; 6517 if (i < 9) 6518 menu_char = '1' + i; 6519 StreamString thread_menu_title; 6520 thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID()); 6521 const char *thread_name = thread_sp->GetName(); 6522 if (thread_name && thread_name[0]) 6523 thread_menu_title.Printf(" %s", thread_name); 6524 else { 6525 const char *queue_name = thread_sp->GetQueueName(); 6526 if (queue_name && queue_name[0]) 6527 thread_menu_title.Printf(" %s", queue_name); 6528 } 6529 menu.AddSubmenu( 6530 MenuSP(new Menu(thread_menu_title.GetString().str().c_str(), 6531 nullptr, menu_char, thread_sp->GetID()))); 6532 } 6533 } else if (submenus.size() > 7) { 6534 // Remove the separator and any other thread submenu items that were 6535 // previously added 6536 submenus.erase(submenus.begin() + 7, submenus.end()); 6537 } 6538 // Since we are adding and removing items we need to recalculate the 6539 // name lengths 6540 menu.RecalculateNameLengths(); 6541 } 6542 return MenuActionResult::Handled; 6543 6544 case eMenuID_ViewVariables: { 6545 WindowSP main_window_sp = m_app.GetMainWindow(); 6546 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 6547 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 6548 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 6549 const Rect source_bounds = source_window_sp->GetBounds(); 6550 6551 if (variables_window_sp) { 6552 const Rect variables_bounds = variables_window_sp->GetBounds(); 6553 6554 main_window_sp->RemoveSubWindow(variables_window_sp.get()); 6555 6556 if (registers_window_sp) { 6557 // We have a registers window, so give all the area back to the 6558 // registers window 6559 Rect registers_bounds = variables_bounds; 6560 registers_bounds.size.width = source_bounds.size.width; 6561 registers_window_sp->SetBounds(registers_bounds); 6562 } else { 6563 // We have no registers window showing so give the bottom area back 6564 // to the source view 6565 source_window_sp->Resize(source_bounds.size.width, 6566 source_bounds.size.height + 6567 variables_bounds.size.height); 6568 } 6569 } else { 6570 Rect new_variables_rect; 6571 if (registers_window_sp) { 6572 // We have a registers window so split the area of the registers 6573 // window into two columns where the left hand side will be the 6574 // variables and the right hand side will be the registers 6575 const Rect variables_bounds = registers_window_sp->GetBounds(); 6576 Rect new_registers_rect; 6577 variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect, 6578 new_registers_rect); 6579 registers_window_sp->SetBounds(new_registers_rect); 6580 } else { 6581 // No registers window, grab the bottom part of the source window 6582 Rect new_source_rect; 6583 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, 6584 new_variables_rect); 6585 source_window_sp->SetBounds(new_source_rect); 6586 } 6587 WindowSP new_window_sp = main_window_sp->CreateSubWindow( 6588 "Variables", new_variables_rect, false); 6589 new_window_sp->SetDelegate( 6590 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 6591 } 6592 touchwin(stdscr); 6593 } 6594 return MenuActionResult::Handled; 6595 6596 case eMenuID_ViewRegisters: { 6597 WindowSP main_window_sp = m_app.GetMainWindow(); 6598 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 6599 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 6600 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 6601 const Rect source_bounds = source_window_sp->GetBounds(); 6602 6603 if (registers_window_sp) { 6604 if (variables_window_sp) { 6605 const Rect variables_bounds = variables_window_sp->GetBounds(); 6606 6607 // We have a variables window, so give all the area back to the 6608 // variables window 6609 variables_window_sp->Resize(variables_bounds.size.width + 6610 registers_window_sp->GetWidth(), 6611 variables_bounds.size.height); 6612 } else { 6613 // We have no variables window showing so give the bottom area back 6614 // to the source view 6615 source_window_sp->Resize(source_bounds.size.width, 6616 source_bounds.size.height + 6617 registers_window_sp->GetHeight()); 6618 } 6619 main_window_sp->RemoveSubWindow(registers_window_sp.get()); 6620 } else { 6621 Rect new_regs_rect; 6622 if (variables_window_sp) { 6623 // We have a variables window, split it into two columns where the 6624 // left hand side will be the variables and the right hand side will 6625 // be the registers 6626 const Rect variables_bounds = variables_window_sp->GetBounds(); 6627 Rect new_vars_rect; 6628 variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect, 6629 new_regs_rect); 6630 variables_window_sp->SetBounds(new_vars_rect); 6631 } else { 6632 // No variables window, grab the bottom part of the source window 6633 Rect new_source_rect; 6634 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, 6635 new_regs_rect); 6636 source_window_sp->SetBounds(new_source_rect); 6637 } 6638 WindowSP new_window_sp = 6639 main_window_sp->CreateSubWindow("Registers", new_regs_rect, false); 6640 new_window_sp->SetDelegate( 6641 WindowDelegateSP(new RegistersWindowDelegate(m_debugger))); 6642 } 6643 touchwin(stdscr); 6644 } 6645 return MenuActionResult::Handled; 6646 6647 case eMenuID_ViewBreakpoints: { 6648 WindowSP main_window_sp = m_app.GetMainWindow(); 6649 WindowSP threads_window_sp = main_window_sp->FindSubWindow("Threads"); 6650 WindowSP breakpoints_window_sp = 6651 main_window_sp->FindSubWindow("Breakpoints"); 6652 const Rect threads_bounds = threads_window_sp->GetBounds(); 6653 6654 // If a breakpoints window already exists, remove it and give the area 6655 // it used to occupy to the threads window. If it doesn't exist, split 6656 // the threads window horizontally into two windows where the top window 6657 // is the threads window and the bottom window is a newly added 6658 // breakpoints window. 6659 if (breakpoints_window_sp) { 6660 threads_window_sp->Resize(threads_bounds.size.width, 6661 threads_bounds.size.height + 6662 breakpoints_window_sp->GetHeight()); 6663 main_window_sp->RemoveSubWindow(breakpoints_window_sp.get()); 6664 } else { 6665 Rect new_threads_bounds, breakpoints_bounds; 6666 threads_bounds.HorizontalSplitPercentage(0.70, new_threads_bounds, 6667 breakpoints_bounds); 6668 threads_window_sp->SetBounds(new_threads_bounds); 6669 breakpoints_window_sp = main_window_sp->CreateSubWindow( 6670 "Breakpoints", breakpoints_bounds, false); 6671 TreeDelegateSP breakpoints_delegate_sp( 6672 new BreakpointsTreeDelegate(m_debugger)); 6673 breakpoints_window_sp->SetDelegate(WindowDelegateSP( 6674 new TreeWindowDelegate(m_debugger, breakpoints_delegate_sp))); 6675 } 6676 touchwin(stdscr); 6677 return MenuActionResult::Handled; 6678 } 6679 6680 case eMenuID_HelpGUIHelp: 6681 m_app.GetMainWindow()->CreateHelpSubwindow(); 6682 return MenuActionResult::Handled; 6683 6684 default: 6685 break; 6686 } 6687 6688 return MenuActionResult::NotHandled; 6689 } 6690 6691 protected: 6692 Application &m_app; 6693 Debugger &m_debugger; 6694 }; 6695 6696 class StatusBarWindowDelegate : public WindowDelegate { 6697 public: 6698 StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) { 6699 FormatEntity::Parse("Thread: ${thread.id%tid}", m_format); 6700 } 6701 6702 ~StatusBarWindowDelegate() override = default; 6703 6704 bool WindowDelegateDraw(Window &window, bool force) override { 6705 ExecutionContext exe_ctx = 6706 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6707 Process *process = exe_ctx.GetProcessPtr(); 6708 Thread *thread = exe_ctx.GetThreadPtr(); 6709 StackFrame *frame = exe_ctx.GetFramePtr(); 6710 window.Erase(); 6711 window.SetBackground(BlackOnWhite); 6712 window.MoveCursor(0, 0); 6713 if (process) { 6714 const StateType state = process->GetState(); 6715 window.Printf("Process: %5" PRIu64 " %10s", process->GetID(), 6716 StateAsCString(state)); 6717 6718 if (StateIsStoppedState(state, true)) { 6719 StreamString strm; 6720 if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, 6721 nullptr, nullptr, false, false)) { 6722 window.MoveCursor(40, 0); 6723 window.PutCStringTruncated(1, strm.GetString().str().c_str()); 6724 } 6725 6726 window.MoveCursor(60, 0); 6727 if (frame) 6728 window.Printf("Frame: %3u PC = 0x%16.16" PRIx64, 6729 frame->GetFrameIndex(), 6730 frame->GetFrameCodeAddress().GetOpcodeLoadAddress( 6731 exe_ctx.GetTargetPtr())); 6732 } else if (state == eStateExited) { 6733 const char *exit_desc = process->GetExitDescription(); 6734 const int exit_status = process->GetExitStatus(); 6735 if (exit_desc && exit_desc[0]) 6736 window.Printf(" with status = %i (%s)", exit_status, exit_desc); 6737 else 6738 window.Printf(" with status = %i", exit_status); 6739 } 6740 } 6741 return true; 6742 } 6743 6744 protected: 6745 Debugger &m_debugger; 6746 FormatEntity::Entry m_format; 6747 }; 6748 6749 class SourceFileWindowDelegate : public WindowDelegate { 6750 public: 6751 SourceFileWindowDelegate(Debugger &debugger) 6752 : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(), 6753 m_disassembly_sp(), m_disassembly_range(), m_title() {} 6754 6755 ~SourceFileWindowDelegate() override = default; 6756 6757 void Update(const SymbolContext &sc) { m_sc = sc; } 6758 6759 uint32_t NumVisibleLines() const { return m_max_y - m_min_y; } 6760 6761 const char *WindowDelegateGetHelpText() override { 6762 return "Source/Disassembly window keyboard shortcuts:"; 6763 } 6764 6765 KeyHelp *WindowDelegateGetKeyHelp() override { 6766 static curses::KeyHelp g_source_view_key_help[] = { 6767 {KEY_RETURN, "Run to selected line with one shot breakpoint"}, 6768 {KEY_UP, "Select previous source line"}, 6769 {KEY_DOWN, "Select next source line"}, 6770 {KEY_LEFT, "Scroll to the left"}, 6771 {KEY_RIGHT, "Scroll to the right"}, 6772 {KEY_PPAGE, "Page up"}, 6773 {KEY_NPAGE, "Page down"}, 6774 {'b', "Set breakpoint on selected source/disassembly line"}, 6775 {'c', "Continue process"}, 6776 {'D', "Detach with process suspended"}, 6777 {'h', "Show help dialog"}, 6778 {'n', "Step over (source line)"}, 6779 {'N', "Step over (single instruction)"}, 6780 {'f', "Step out (finish)"}, 6781 {'s', "Step in (source line)"}, 6782 {'S', "Step in (single instruction)"}, 6783 {'u', "Frame up"}, 6784 {'d', "Frame down"}, 6785 {',', "Page up"}, 6786 {'.', "Page down"}, 6787 {'\0', nullptr}}; 6788 return g_source_view_key_help; 6789 } 6790 6791 bool WindowDelegateDraw(Window &window, bool force) override { 6792 ExecutionContext exe_ctx = 6793 m_debugger.GetCommandInterpreter().GetExecutionContext(); 6794 Process *process = exe_ctx.GetProcessPtr(); 6795 Thread *thread = nullptr; 6796 6797 bool update_location = false; 6798 if (process) { 6799 StateType state = process->GetState(); 6800 if (StateIsStoppedState(state, true)) { 6801 // We are stopped, so it is ok to 6802 update_location = true; 6803 } 6804 } 6805 6806 m_min_x = 1; 6807 m_min_y = 2; 6808 m_max_x = window.GetMaxX() - 1; 6809 m_max_y = window.GetMaxY() - 1; 6810 6811 const uint32_t num_visible_lines = NumVisibleLines(); 6812 StackFrameSP frame_sp; 6813 bool set_selected_line_to_pc = false; 6814 6815 if (update_location) { 6816 const bool process_alive = process ? process->IsAlive() : false; 6817 bool thread_changed = false; 6818 if (process_alive) { 6819 thread = exe_ctx.GetThreadPtr(); 6820 if (thread) { 6821 frame_sp = thread->GetSelectedFrame(); 6822 auto tid = thread->GetID(); 6823 thread_changed = tid != m_tid; 6824 m_tid = tid; 6825 } else { 6826 if (m_tid != LLDB_INVALID_THREAD_ID) { 6827 thread_changed = true; 6828 m_tid = LLDB_INVALID_THREAD_ID; 6829 } 6830 } 6831 } 6832 const uint32_t stop_id = process ? process->GetStopID() : 0; 6833 const bool stop_id_changed = stop_id != m_stop_id; 6834 bool frame_changed = false; 6835 m_stop_id = stop_id; 6836 m_title.Clear(); 6837 if (frame_sp) { 6838 m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything); 6839 if (m_sc.module_sp) { 6840 m_title.Printf( 6841 "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString()); 6842 ConstString func_name = m_sc.GetFunctionName(); 6843 if (func_name) 6844 m_title.Printf("`%s", func_name.GetCString()); 6845 } 6846 const uint32_t frame_idx = frame_sp->GetFrameIndex(); 6847 frame_changed = frame_idx != m_frame_idx; 6848 m_frame_idx = frame_idx; 6849 } else { 6850 m_sc.Clear(true); 6851 frame_changed = m_frame_idx != UINT32_MAX; 6852 m_frame_idx = UINT32_MAX; 6853 } 6854 6855 const bool context_changed = 6856 thread_changed || frame_changed || stop_id_changed; 6857 6858 if (process_alive) { 6859 if (m_sc.line_entry.IsValid()) { 6860 m_pc_line = m_sc.line_entry.line; 6861 if (m_pc_line != UINT32_MAX) 6862 --m_pc_line; // Convert to zero based line number... 6863 // Update the selected line if the stop ID changed... 6864 if (context_changed) 6865 m_selected_line = m_pc_line; 6866 6867 if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) { 6868 // Same file, nothing to do, we should either have the lines or 6869 // not (source file missing) 6870 if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) { 6871 if (m_selected_line >= m_first_visible_line + num_visible_lines) 6872 m_first_visible_line = m_selected_line - 10; 6873 } else { 6874 if (m_selected_line > 10) 6875 m_first_visible_line = m_selected_line - 10; 6876 else 6877 m_first_visible_line = 0; 6878 } 6879 } else { 6880 // File changed, set selected line to the line with the PC 6881 m_selected_line = m_pc_line; 6882 m_file_sp = 6883 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file); 6884 if (m_file_sp) { 6885 const size_t num_lines = m_file_sp->GetNumLines(); 6886 m_line_width = 1; 6887 for (size_t n = num_lines; n >= 10; n = n / 10) 6888 ++m_line_width; 6889 6890 if (num_lines < num_visible_lines || 6891 m_selected_line < num_visible_lines) 6892 m_first_visible_line = 0; 6893 else 6894 m_first_visible_line = m_selected_line - 10; 6895 } 6896 } 6897 } else { 6898 m_file_sp.reset(); 6899 } 6900 6901 if (!m_file_sp || m_file_sp->GetNumLines() == 0) { 6902 // Show disassembly 6903 bool prefer_file_cache = false; 6904 if (m_sc.function) { 6905 if (m_disassembly_scope != m_sc.function) { 6906 m_disassembly_scope = m_sc.function; 6907 m_disassembly_sp = m_sc.function->GetInstructions( 6908 exe_ctx, nullptr, !prefer_file_cache); 6909 if (m_disassembly_sp) { 6910 set_selected_line_to_pc = true; 6911 m_disassembly_range = m_sc.function->GetAddressRange(); 6912 } else { 6913 m_disassembly_range.Clear(); 6914 } 6915 } else { 6916 set_selected_line_to_pc = context_changed; 6917 } 6918 } else if (m_sc.symbol) { 6919 if (m_disassembly_scope != m_sc.symbol) { 6920 m_disassembly_scope = m_sc.symbol; 6921 m_disassembly_sp = m_sc.symbol->GetInstructions( 6922 exe_ctx, nullptr, prefer_file_cache); 6923 if (m_disassembly_sp) { 6924 set_selected_line_to_pc = true; 6925 m_disassembly_range.GetBaseAddress() = 6926 m_sc.symbol->GetAddress(); 6927 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize()); 6928 } else { 6929 m_disassembly_range.Clear(); 6930 } 6931 } else { 6932 set_selected_line_to_pc = context_changed; 6933 } 6934 } 6935 } 6936 } else { 6937 m_pc_line = UINT32_MAX; 6938 } 6939 } 6940 6941 const int window_width = window.GetWidth(); 6942 window.Erase(); 6943 window.DrawTitleBox("Sources"); 6944 if (!m_title.GetString().empty()) { 6945 window.AttributeOn(A_REVERSE); 6946 window.MoveCursor(1, 1); 6947 window.PutChar(' '); 6948 window.PutCStringTruncated(1, m_title.GetString().str().c_str()); 6949 int x = window.GetCursorX(); 6950 if (x < window_width - 1) { 6951 window.Printf("%*s", window_width - x - 1, ""); 6952 } 6953 window.AttributeOff(A_REVERSE); 6954 } 6955 6956 Target *target = exe_ctx.GetTargetPtr(); 6957 const size_t num_source_lines = GetNumSourceLines(); 6958 if (num_source_lines > 0) { 6959 // Display source 6960 BreakpointLines bp_lines; 6961 if (target) { 6962 BreakpointList &bp_list = target->GetBreakpointList(); 6963 const size_t num_bps = bp_list.GetSize(); 6964 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 6965 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 6966 const size_t num_bps_locs = bp_sp->GetNumLocations(); 6967 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 6968 BreakpointLocationSP bp_loc_sp = 6969 bp_sp->GetLocationAtIndex(bp_loc_idx); 6970 LineEntry bp_loc_line_entry; 6971 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( 6972 bp_loc_line_entry)) { 6973 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) { 6974 bp_lines.insert(bp_loc_line_entry.line); 6975 } 6976 } 6977 } 6978 } 6979 } 6980 6981 for (size_t i = 0; i < num_visible_lines; ++i) { 6982 const uint32_t curr_line = m_first_visible_line + i; 6983 if (curr_line < num_source_lines) { 6984 const int line_y = m_min_y + i; 6985 window.MoveCursor(1, line_y); 6986 const bool is_pc_line = curr_line == m_pc_line; 6987 const bool line_is_selected = m_selected_line == curr_line; 6988 // Highlight the line as the PC line first (done by passing 6989 // argument to OutputColoredStringTruncated()), then if the selected 6990 // line isn't the same as the PC line, highlight it differently. 6991 attr_t highlight_attr = 0; 6992 attr_t bp_attr = 0; 6993 if (line_is_selected && !is_pc_line) 6994 highlight_attr = A_REVERSE; 6995 6996 if (bp_lines.find(curr_line + 1) != bp_lines.end()) 6997 bp_attr = COLOR_PAIR(BlackOnWhite); 6998 6999 if (bp_attr) 7000 window.AttributeOn(bp_attr); 7001 7002 window.Printf(" %*u ", m_line_width, curr_line + 1); 7003 7004 if (bp_attr) 7005 window.AttributeOff(bp_attr); 7006 7007 window.PutChar(ACS_VLINE); 7008 // Mark the line with the PC with a diamond 7009 if (is_pc_line) 7010 window.PutChar(ACS_DIAMOND); 7011 else 7012 window.PutChar(' '); 7013 7014 if (highlight_attr) 7015 window.AttributeOn(highlight_attr); 7016 7017 StreamString lineStream; 7018 7019 llvm::Optional<size_t> column; 7020 if (is_pc_line && m_sc.line_entry.IsValid() && m_sc.line_entry.column) 7021 column = m_sc.line_entry.column - 1; 7022 m_file_sp->DisplaySourceLines(curr_line + 1, column, 0, 0, 7023 &lineStream); 7024 StringRef line = lineStream.GetString(); 7025 if (line.endswith("\n")) 7026 line = line.drop_back(); 7027 bool wasWritten = window.OutputColoredStringTruncated( 7028 1, line, m_first_visible_column, is_pc_line); 7029 if (!wasWritten && (line_is_selected || is_pc_line)) { 7030 // Draw an empty space to show the selected/PC line if empty, 7031 // or draw '<' if nothing is visible because of scrolling too much 7032 // to the right. 7033 window.PutCStringTruncated( 7034 1, line.empty() && m_first_visible_column == 0 ? " " : "<"); 7035 } 7036 7037 if (is_pc_line && frame_sp && 7038 frame_sp->GetConcreteFrameIndex() == 0) { 7039 StopInfoSP stop_info_sp; 7040 if (thread) 7041 stop_info_sp = thread->GetStopInfo(); 7042 if (stop_info_sp) { 7043 const char *stop_description = stop_info_sp->GetDescription(); 7044 if (stop_description && stop_description[0]) { 7045 size_t stop_description_len = strlen(stop_description); 7046 int desc_x = window_width - stop_description_len - 16; 7047 if (desc_x - window.GetCursorX() > 0) 7048 window.Printf("%*s", desc_x - window.GetCursorX(), ""); 7049 window.MoveCursor(window_width - stop_description_len - 16, 7050 line_y); 7051 const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue); 7052 window.AttributeOn(stop_reason_attr); 7053 window.PrintfTruncated(1, " <<< Thread %u: %s ", 7054 thread->GetIndexID(), stop_description); 7055 window.AttributeOff(stop_reason_attr); 7056 } 7057 } else { 7058 window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); 7059 } 7060 } 7061 if (highlight_attr) 7062 window.AttributeOff(highlight_attr); 7063 } else { 7064 break; 7065 } 7066 } 7067 } else { 7068 size_t num_disassembly_lines = GetNumDisassemblyLines(); 7069 if (num_disassembly_lines > 0) { 7070 // Display disassembly 7071 BreakpointAddrs bp_file_addrs; 7072 Target *target = exe_ctx.GetTargetPtr(); 7073 if (target) { 7074 BreakpointList &bp_list = target->GetBreakpointList(); 7075 const size_t num_bps = bp_list.GetSize(); 7076 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 7077 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 7078 const size_t num_bps_locs = bp_sp->GetNumLocations(); 7079 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; 7080 ++bp_loc_idx) { 7081 BreakpointLocationSP bp_loc_sp = 7082 bp_sp->GetLocationAtIndex(bp_loc_idx); 7083 LineEntry bp_loc_line_entry; 7084 const lldb::addr_t file_addr = 7085 bp_loc_sp->GetAddress().GetFileAddress(); 7086 if (file_addr != LLDB_INVALID_ADDRESS) { 7087 if (m_disassembly_range.ContainsFileAddress(file_addr)) 7088 bp_file_addrs.insert(file_addr); 7089 } 7090 } 7091 } 7092 } 7093 7094 const attr_t selected_highlight_attr = A_REVERSE; 7095 const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue); 7096 7097 StreamString strm; 7098 7099 InstructionList &insts = m_disassembly_sp->GetInstructionList(); 7100 Address pc_address; 7101 7102 if (frame_sp) 7103 pc_address = frame_sp->GetFrameCodeAddress(); 7104 const uint32_t pc_idx = 7105 pc_address.IsValid() 7106 ? insts.GetIndexOfInstructionAtAddress(pc_address) 7107 : UINT32_MAX; 7108 if (set_selected_line_to_pc) { 7109 m_selected_line = pc_idx; 7110 } 7111 7112 const uint32_t non_visible_pc_offset = (num_visible_lines / 5); 7113 if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines) 7114 m_first_visible_line = 0; 7115 7116 if (pc_idx < num_disassembly_lines) { 7117 if (pc_idx < static_cast<uint32_t>(m_first_visible_line) || 7118 pc_idx >= m_first_visible_line + num_visible_lines) 7119 m_first_visible_line = pc_idx - non_visible_pc_offset; 7120 } 7121 7122 for (size_t i = 0; i < num_visible_lines; ++i) { 7123 const uint32_t inst_idx = m_first_visible_line + i; 7124 Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get(); 7125 if (!inst) 7126 break; 7127 7128 const int line_y = m_min_y + i; 7129 window.MoveCursor(1, line_y); 7130 const bool is_pc_line = frame_sp && inst_idx == pc_idx; 7131 const bool line_is_selected = m_selected_line == inst_idx; 7132 // Highlight the line as the PC line first, then if the selected 7133 // line isn't the same as the PC line, highlight it differently 7134 attr_t highlight_attr = 0; 7135 attr_t bp_attr = 0; 7136 if (is_pc_line) 7137 highlight_attr = pc_highlight_attr; 7138 else if (line_is_selected) 7139 highlight_attr = selected_highlight_attr; 7140 7141 if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) != 7142 bp_file_addrs.end()) 7143 bp_attr = COLOR_PAIR(BlackOnWhite); 7144 7145 if (bp_attr) 7146 window.AttributeOn(bp_attr); 7147 7148 window.Printf(" 0x%16.16llx ", 7149 static_cast<unsigned long long>( 7150 inst->GetAddress().GetLoadAddress(target))); 7151 7152 if (bp_attr) 7153 window.AttributeOff(bp_attr); 7154 7155 window.PutChar(ACS_VLINE); 7156 // Mark the line with the PC with a diamond 7157 if (is_pc_line) 7158 window.PutChar(ACS_DIAMOND); 7159 else 7160 window.PutChar(' '); 7161 7162 if (highlight_attr) 7163 window.AttributeOn(highlight_attr); 7164 7165 const char *mnemonic = inst->GetMnemonic(&exe_ctx); 7166 const char *operands = inst->GetOperands(&exe_ctx); 7167 const char *comment = inst->GetComment(&exe_ctx); 7168 7169 if (mnemonic != nullptr && mnemonic[0] == '\0') 7170 mnemonic = nullptr; 7171 if (operands != nullptr && operands[0] == '\0') 7172 operands = nullptr; 7173 if (comment != nullptr && comment[0] == '\0') 7174 comment = nullptr; 7175 7176 strm.Clear(); 7177 7178 if (mnemonic != nullptr && operands != nullptr && comment != nullptr) 7179 strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment); 7180 else if (mnemonic != nullptr && operands != nullptr) 7181 strm.Printf("%-8s %s", mnemonic, operands); 7182 else if (mnemonic != nullptr) 7183 strm.Printf("%s", mnemonic); 7184 7185 int right_pad = 1; 7186 window.PutCStringTruncated( 7187 right_pad, 7188 strm.GetString().substr(m_first_visible_column).data()); 7189 7190 if (is_pc_line && frame_sp && 7191 frame_sp->GetConcreteFrameIndex() == 0) { 7192 StopInfoSP stop_info_sp; 7193 if (thread) 7194 stop_info_sp = thread->GetStopInfo(); 7195 if (stop_info_sp) { 7196 const char *stop_description = stop_info_sp->GetDescription(); 7197 if (stop_description && stop_description[0]) { 7198 size_t stop_description_len = strlen(stop_description); 7199 int desc_x = window_width - stop_description_len - 16; 7200 if (desc_x - window.GetCursorX() > 0) 7201 window.Printf("%*s", desc_x - window.GetCursorX(), ""); 7202 window.MoveCursor(window_width - stop_description_len - 15, 7203 line_y); 7204 window.PrintfTruncated(1, "<<< Thread %u: %s ", 7205 thread->GetIndexID(), stop_description); 7206 } 7207 } else { 7208 window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); 7209 } 7210 } 7211 if (highlight_attr) 7212 window.AttributeOff(highlight_attr); 7213 } 7214 } 7215 } 7216 return true; // Drawing handled 7217 } 7218 7219 size_t GetNumLines() { 7220 size_t num_lines = GetNumSourceLines(); 7221 if (num_lines == 0) 7222 num_lines = GetNumDisassemblyLines(); 7223 return num_lines; 7224 } 7225 7226 size_t GetNumSourceLines() const { 7227 if (m_file_sp) 7228 return m_file_sp->GetNumLines(); 7229 return 0; 7230 } 7231 7232 size_t GetNumDisassemblyLines() const { 7233 if (m_disassembly_sp) 7234 return m_disassembly_sp->GetInstructionList().GetSize(); 7235 return 0; 7236 } 7237 7238 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 7239 const uint32_t num_visible_lines = NumVisibleLines(); 7240 const size_t num_lines = GetNumLines(); 7241 7242 switch (c) { 7243 case ',': 7244 case KEY_PPAGE: 7245 // Page up key 7246 if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines) 7247 m_first_visible_line -= num_visible_lines; 7248 else 7249 m_first_visible_line = 0; 7250 m_selected_line = m_first_visible_line; 7251 return eKeyHandled; 7252 7253 case '.': 7254 case KEY_NPAGE: 7255 // Page down key 7256 { 7257 if (m_first_visible_line + num_visible_lines < num_lines) 7258 m_first_visible_line += num_visible_lines; 7259 else if (num_lines < num_visible_lines) 7260 m_first_visible_line = 0; 7261 else 7262 m_first_visible_line = num_lines - num_visible_lines; 7263 m_selected_line = m_first_visible_line; 7264 } 7265 return eKeyHandled; 7266 7267 case KEY_UP: 7268 if (m_selected_line > 0) { 7269 m_selected_line--; 7270 if (static_cast<size_t>(m_first_visible_line) > m_selected_line) 7271 m_first_visible_line = m_selected_line; 7272 } 7273 return eKeyHandled; 7274 7275 case KEY_DOWN: 7276 if (m_selected_line + 1 < num_lines) { 7277 m_selected_line++; 7278 if (m_first_visible_line + num_visible_lines < m_selected_line) 7279 m_first_visible_line++; 7280 } 7281 return eKeyHandled; 7282 7283 case KEY_LEFT: 7284 if (m_first_visible_column > 0) 7285 --m_first_visible_column; 7286 return eKeyHandled; 7287 7288 case KEY_RIGHT: 7289 ++m_first_visible_column; 7290 return eKeyHandled; 7291 7292 case '\r': 7293 case '\n': 7294 case KEY_ENTER: 7295 // Set a breakpoint and run to the line using a one shot breakpoint 7296 if (GetNumSourceLines() > 0) { 7297 ExecutionContext exe_ctx = 7298 m_debugger.GetCommandInterpreter().GetExecutionContext(); 7299 if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) { 7300 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 7301 nullptr, // Don't limit the breakpoint to certain modules 7302 m_file_sp->GetFileSpec(), // Source file 7303 m_selected_line + 7304 1, // Source line number (m_selected_line is zero based) 7305 0, // Unspecified column. 7306 0, // No offset 7307 eLazyBoolCalculate, // Check inlines using global setting 7308 eLazyBoolCalculate, // Skip prologue using global setting, 7309 false, // internal 7310 false, // request_hardware 7311 eLazyBoolCalculate); // move_to_nearest_code 7312 // Make breakpoint one shot 7313 bp_sp->GetOptions().SetOneShot(true); 7314 exe_ctx.GetProcessRef().Resume(); 7315 } 7316 } else if (m_selected_line < GetNumDisassemblyLines()) { 7317 const Instruction *inst = m_disassembly_sp->GetInstructionList() 7318 .GetInstructionAtIndex(m_selected_line) 7319 .get(); 7320 ExecutionContext exe_ctx = 7321 m_debugger.GetCommandInterpreter().GetExecutionContext(); 7322 if (exe_ctx.HasTargetScope()) { 7323 Address addr = inst->GetAddress(); 7324 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 7325 addr, // lldb_private::Address 7326 false, // internal 7327 false); // request_hardware 7328 // Make breakpoint one shot 7329 bp_sp->GetOptions().SetOneShot(true); 7330 exe_ctx.GetProcessRef().Resume(); 7331 } 7332 } 7333 return eKeyHandled; 7334 7335 case 'b': // 'b' == toggle breakpoint on currently selected line 7336 ToggleBreakpointOnSelectedLine(); 7337 return eKeyHandled; 7338 7339 case 'D': // 'D' == detach and keep stopped 7340 { 7341 ExecutionContext exe_ctx = 7342 m_debugger.GetCommandInterpreter().GetExecutionContext(); 7343 if (exe_ctx.HasProcessScope()) 7344 exe_ctx.GetProcessRef().Detach(true); 7345 } 7346 return eKeyHandled; 7347 7348 case 'c': 7349 // 'c' == continue 7350 { 7351 ExecutionContext exe_ctx = 7352 m_debugger.GetCommandInterpreter().GetExecutionContext(); 7353 if (exe_ctx.HasProcessScope()) 7354 exe_ctx.GetProcessRef().Resume(); 7355 } 7356 return eKeyHandled; 7357 7358 case 'f': 7359 // 'f' == step out (finish) 7360 { 7361 ExecutionContext exe_ctx = 7362 m_debugger.GetCommandInterpreter().GetExecutionContext(); 7363 if (exe_ctx.HasThreadScope() && 7364 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 7365 exe_ctx.GetThreadRef().StepOut(); 7366 } 7367 } 7368 return eKeyHandled; 7369 7370 case 'n': // 'n' == step over 7371 case 'N': // 'N' == step over instruction 7372 { 7373 ExecutionContext exe_ctx = 7374 m_debugger.GetCommandInterpreter().GetExecutionContext(); 7375 if (exe_ctx.HasThreadScope() && 7376 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 7377 bool source_step = (c == 'n'); 7378 exe_ctx.GetThreadRef().StepOver(source_step); 7379 } 7380 } 7381 return eKeyHandled; 7382 7383 case 's': // 's' == step into 7384 case 'S': // 'S' == step into instruction 7385 { 7386 ExecutionContext exe_ctx = 7387 m_debugger.GetCommandInterpreter().GetExecutionContext(); 7388 if (exe_ctx.HasThreadScope() && 7389 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 7390 bool source_step = (c == 's'); 7391 exe_ctx.GetThreadRef().StepIn(source_step); 7392 } 7393 } 7394 return eKeyHandled; 7395 7396 case 'u': // 'u' == frame up 7397 case 'd': // 'd' == frame down 7398 { 7399 ExecutionContext exe_ctx = 7400 m_debugger.GetCommandInterpreter().GetExecutionContext(); 7401 if (exe_ctx.HasThreadScope()) { 7402 Thread *thread = exe_ctx.GetThreadPtr(); 7403 uint32_t frame_idx = thread->GetSelectedFrameIndex(); 7404 if (frame_idx == UINT32_MAX) 7405 frame_idx = 0; 7406 if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount()) 7407 ++frame_idx; 7408 else if (c == 'd' && frame_idx > 0) 7409 --frame_idx; 7410 if (thread->SetSelectedFrameByIndex(frame_idx, true)) 7411 exe_ctx.SetFrameSP(thread->GetSelectedFrame()); 7412 } 7413 } 7414 return eKeyHandled; 7415 7416 case 'h': 7417 window.CreateHelpSubwindow(); 7418 return eKeyHandled; 7419 7420 default: 7421 break; 7422 } 7423 return eKeyNotHandled; 7424 } 7425 7426 void ToggleBreakpointOnSelectedLine() { 7427 ExecutionContext exe_ctx = 7428 m_debugger.GetCommandInterpreter().GetExecutionContext(); 7429 if (!exe_ctx.HasTargetScope()) 7430 return; 7431 if (GetNumSourceLines() > 0) { 7432 // Source file breakpoint. 7433 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); 7434 const size_t num_bps = bp_list.GetSize(); 7435 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 7436 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 7437 const size_t num_bps_locs = bp_sp->GetNumLocations(); 7438 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 7439 BreakpointLocationSP bp_loc_sp = 7440 bp_sp->GetLocationAtIndex(bp_loc_idx); 7441 LineEntry bp_loc_line_entry; 7442 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( 7443 bp_loc_line_entry)) { 7444 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file && 7445 m_selected_line + 1 == bp_loc_line_entry.line) { 7446 bool removed = 7447 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); 7448 assert(removed); 7449 UNUSED_IF_ASSERT_DISABLED(removed); 7450 return; // Existing breakpoint removed. 7451 } 7452 } 7453 } 7454 } 7455 // No breakpoint found on the location, add it. 7456 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 7457 nullptr, // Don't limit the breakpoint to certain modules 7458 m_file_sp->GetFileSpec(), // Source file 7459 m_selected_line + 7460 1, // Source line number (m_selected_line is zero based) 7461 0, // No column specified. 7462 0, // No offset 7463 eLazyBoolCalculate, // Check inlines using global setting 7464 eLazyBoolCalculate, // Skip prologue using global setting, 7465 false, // internal 7466 false, // request_hardware 7467 eLazyBoolCalculate); // move_to_nearest_code 7468 } else { 7469 // Disassembly breakpoint. 7470 assert(GetNumDisassemblyLines() > 0); 7471 assert(m_selected_line < GetNumDisassemblyLines()); 7472 const Instruction *inst = m_disassembly_sp->GetInstructionList() 7473 .GetInstructionAtIndex(m_selected_line) 7474 .get(); 7475 Address addr = inst->GetAddress(); 7476 // Try to find it. 7477 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); 7478 const size_t num_bps = bp_list.GetSize(); 7479 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 7480 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 7481 const size_t num_bps_locs = bp_sp->GetNumLocations(); 7482 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 7483 BreakpointLocationSP bp_loc_sp = 7484 bp_sp->GetLocationAtIndex(bp_loc_idx); 7485 LineEntry bp_loc_line_entry; 7486 const lldb::addr_t file_addr = 7487 bp_loc_sp->GetAddress().GetFileAddress(); 7488 if (file_addr == addr.GetFileAddress()) { 7489 bool removed = 7490 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); 7491 assert(removed); 7492 UNUSED_IF_ASSERT_DISABLED(removed); 7493 return; // Existing breakpoint removed. 7494 } 7495 } 7496 } 7497 // No breakpoint found on the address, add it. 7498 BreakpointSP bp_sp = 7499 exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address 7500 false, // internal 7501 false); // request_hardware 7502 } 7503 } 7504 7505 protected: 7506 typedef std::set<uint32_t> BreakpointLines; 7507 typedef std::set<lldb::addr_t> BreakpointAddrs; 7508 7509 Debugger &m_debugger; 7510 SymbolContext m_sc; 7511 SourceManager::FileSP m_file_sp; 7512 SymbolContextScope *m_disassembly_scope = nullptr; 7513 lldb::DisassemblerSP m_disassembly_sp; 7514 AddressRange m_disassembly_range; 7515 StreamString m_title; 7516 lldb::user_id_t m_tid = LLDB_INVALID_THREAD_ID; 7517 int m_line_width = 4; 7518 uint32_t m_selected_line = 0; // The selected line 7519 uint32_t m_pc_line = 0; // The line with the PC 7520 uint32_t m_stop_id = 0; 7521 uint32_t m_frame_idx = UINT32_MAX; 7522 int m_first_visible_line = 0; 7523 int m_first_visible_column = 0; 7524 int m_min_x = 0; 7525 int m_min_y = 0; 7526 int m_max_x = 0; 7527 int m_max_y = 0; 7528 }; 7529 7530 DisplayOptions ValueObjectListDelegate::g_options = {true}; 7531 7532 IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger) 7533 : IOHandler(debugger, IOHandler::Type::Curses) {} 7534 7535 void IOHandlerCursesGUI::Activate() { 7536 IOHandler::Activate(); 7537 if (!m_app_ap) { 7538 m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE()); 7539 7540 // This is both a window and a menu delegate 7541 std::shared_ptr<ApplicationDelegate> app_delegate_sp( 7542 new ApplicationDelegate(*m_app_ap, m_debugger)); 7543 7544 MenuDelegateSP app_menu_delegate_sp = 7545 std::static_pointer_cast<MenuDelegate>(app_delegate_sp); 7546 MenuSP lldb_menu_sp( 7547 new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB)); 7548 MenuSP exit_menuitem_sp( 7549 new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit)); 7550 exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit); 7551 lldb_menu_sp->AddSubmenu(MenuSP(new Menu( 7552 "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout))); 7553 lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 7554 lldb_menu_sp->AddSubmenu(exit_menuitem_sp); 7555 7556 MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2), 7557 ApplicationDelegate::eMenuID_Target)); 7558 target_menu_sp->AddSubmenu(MenuSP(new Menu( 7559 "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate))); 7560 target_menu_sp->AddSubmenu(MenuSP(new Menu( 7561 "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete))); 7562 7563 MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3), 7564 ApplicationDelegate::eMenuID_Process)); 7565 process_menu_sp->AddSubmenu(MenuSP(new Menu( 7566 "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach))); 7567 process_menu_sp->AddSubmenu( 7568 MenuSP(new Menu("Detach and resume", nullptr, 'd', 7569 ApplicationDelegate::eMenuID_ProcessDetachResume))); 7570 process_menu_sp->AddSubmenu( 7571 MenuSP(new Menu("Detach suspended", nullptr, 's', 7572 ApplicationDelegate::eMenuID_ProcessDetachSuspended))); 7573 process_menu_sp->AddSubmenu(MenuSP(new Menu( 7574 "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch))); 7575 process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 7576 process_menu_sp->AddSubmenu( 7577 MenuSP(new Menu("Continue", nullptr, 'c', 7578 ApplicationDelegate::eMenuID_ProcessContinue))); 7579 process_menu_sp->AddSubmenu(MenuSP(new Menu( 7580 "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt))); 7581 process_menu_sp->AddSubmenu(MenuSP(new Menu( 7582 "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill))); 7583 7584 MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4), 7585 ApplicationDelegate::eMenuID_Thread)); 7586 thread_menu_sp->AddSubmenu(MenuSP(new Menu( 7587 "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn))); 7588 thread_menu_sp->AddSubmenu( 7589 MenuSP(new Menu("Step Over", nullptr, 'v', 7590 ApplicationDelegate::eMenuID_ThreadStepOver))); 7591 thread_menu_sp->AddSubmenu(MenuSP(new Menu( 7592 "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut))); 7593 7594 MenuSP view_menu_sp( 7595 new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View)); 7596 view_menu_sp->AddSubmenu( 7597 MenuSP(new Menu("Backtrace", nullptr, 't', 7598 ApplicationDelegate::eMenuID_ViewBacktrace))); 7599 view_menu_sp->AddSubmenu( 7600 MenuSP(new Menu("Registers", nullptr, 'r', 7601 ApplicationDelegate::eMenuID_ViewRegisters))); 7602 view_menu_sp->AddSubmenu(MenuSP(new Menu( 7603 "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource))); 7604 view_menu_sp->AddSubmenu( 7605 MenuSP(new Menu("Variables", nullptr, 'v', 7606 ApplicationDelegate::eMenuID_ViewVariables))); 7607 view_menu_sp->AddSubmenu( 7608 MenuSP(new Menu("Breakpoints", nullptr, 'b', 7609 ApplicationDelegate::eMenuID_ViewBreakpoints))); 7610 7611 MenuSP help_menu_sp( 7612 new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help)); 7613 help_menu_sp->AddSubmenu(MenuSP(new Menu( 7614 "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp))); 7615 7616 m_app_ap->Initialize(); 7617 WindowSP &main_window_sp = m_app_ap->GetMainWindow(); 7618 7619 MenuSP menubar_sp(new Menu(Menu::Type::Bar)); 7620 menubar_sp->AddSubmenu(lldb_menu_sp); 7621 menubar_sp->AddSubmenu(target_menu_sp); 7622 menubar_sp->AddSubmenu(process_menu_sp); 7623 menubar_sp->AddSubmenu(thread_menu_sp); 7624 menubar_sp->AddSubmenu(view_menu_sp); 7625 menubar_sp->AddSubmenu(help_menu_sp); 7626 menubar_sp->SetDelegate(app_menu_delegate_sp); 7627 7628 Rect content_bounds = main_window_sp->GetFrame(); 7629 Rect menubar_bounds = content_bounds.MakeMenuBar(); 7630 Rect status_bounds = content_bounds.MakeStatusBar(); 7631 Rect source_bounds; 7632 Rect variables_bounds; 7633 Rect threads_bounds; 7634 Rect source_variables_bounds; 7635 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, 7636 threads_bounds); 7637 source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds, 7638 variables_bounds); 7639 7640 WindowSP menubar_window_sp = 7641 main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false); 7642 // Let the menubar get keys if the active window doesn't handle the keys 7643 // that are typed so it can respond to menubar key presses. 7644 menubar_window_sp->SetCanBeActive( 7645 false); // Don't let the menubar become the active window 7646 menubar_window_sp->SetDelegate(menubar_sp); 7647 7648 WindowSP source_window_sp( 7649 main_window_sp->CreateSubWindow("Source", source_bounds, true)); 7650 WindowSP variables_window_sp( 7651 main_window_sp->CreateSubWindow("Variables", variables_bounds, false)); 7652 WindowSP threads_window_sp( 7653 main_window_sp->CreateSubWindow("Threads", threads_bounds, false)); 7654 WindowSP status_window_sp( 7655 main_window_sp->CreateSubWindow("Status", status_bounds, false)); 7656 status_window_sp->SetCanBeActive( 7657 false); // Don't let the status bar become the active window 7658 main_window_sp->SetDelegate( 7659 std::static_pointer_cast<WindowDelegate>(app_delegate_sp)); 7660 source_window_sp->SetDelegate( 7661 WindowDelegateSP(new SourceFileWindowDelegate(m_debugger))); 7662 variables_window_sp->SetDelegate( 7663 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 7664 TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger)); 7665 threads_window_sp->SetDelegate(WindowDelegateSP( 7666 new TreeWindowDelegate(m_debugger, thread_delegate_sp))); 7667 status_window_sp->SetDelegate( 7668 WindowDelegateSP(new StatusBarWindowDelegate(m_debugger))); 7669 7670 // Show the main help window once the first time the curses GUI is 7671 // launched 7672 static bool g_showed_help = false; 7673 if (!g_showed_help) { 7674 g_showed_help = true; 7675 main_window_sp->CreateHelpSubwindow(); 7676 } 7677 7678 // All colors with black background. 7679 init_pair(1, COLOR_BLACK, COLOR_BLACK); 7680 init_pair(2, COLOR_RED, COLOR_BLACK); 7681 init_pair(3, COLOR_GREEN, COLOR_BLACK); 7682 init_pair(4, COLOR_YELLOW, COLOR_BLACK); 7683 init_pair(5, COLOR_BLUE, COLOR_BLACK); 7684 init_pair(6, COLOR_MAGENTA, COLOR_BLACK); 7685 init_pair(7, COLOR_CYAN, COLOR_BLACK); 7686 init_pair(8, COLOR_WHITE, COLOR_BLACK); 7687 // All colors with blue background. 7688 init_pair(9, COLOR_BLACK, COLOR_BLUE); 7689 init_pair(10, COLOR_RED, COLOR_BLUE); 7690 init_pair(11, COLOR_GREEN, COLOR_BLUE); 7691 init_pair(12, COLOR_YELLOW, COLOR_BLUE); 7692 init_pair(13, COLOR_BLUE, COLOR_BLUE); 7693 init_pair(14, COLOR_MAGENTA, COLOR_BLUE); 7694 init_pair(15, COLOR_CYAN, COLOR_BLUE); 7695 init_pair(16, COLOR_WHITE, COLOR_BLUE); 7696 // These must match the order in the color indexes enum. 7697 init_pair(17, COLOR_BLACK, COLOR_WHITE); 7698 init_pair(18, COLOR_MAGENTA, COLOR_WHITE); 7699 static_assert(LastColorPairIndex == 18, "Color indexes do not match."); 7700 7701 define_key("\033[Z", KEY_SHIFT_TAB); 7702 define_key("\033\015", KEY_ALT_ENTER); 7703 } 7704 } 7705 7706 void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); } 7707 7708 void IOHandlerCursesGUI::Run() { 7709 m_app_ap->Run(m_debugger); 7710 SetIsDone(true); 7711 } 7712 7713 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default; 7714 7715 void IOHandlerCursesGUI::Cancel() {} 7716 7717 bool IOHandlerCursesGUI::Interrupt() { return false; } 7718 7719 void IOHandlerCursesGUI::GotEOF() {} 7720 7721 void IOHandlerCursesGUI::TerminalSizeChanged() { 7722 m_app_ap->TerminalSizeChanged(); 7723 } 7724 7725 #endif // LLDB_ENABLE_CURSES 7726