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