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