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