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