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