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