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