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